Prechádzať zdrojové kódy

fix:卷宗封底/表格居右/

zzw 6 dní pred
rodič
commit
630e29f8cb

+ 1 - 1
assistMg/src/main/java/com/hotent/project/manager/impl/CostProjectTaskMaterialSummaryManagerImpl.java

@@ -39,7 +39,7 @@ public class CostProjectTaskMaterialSummaryManagerImpl extends BaseManagerImpl<C
         queryWrapper.eq(CostProjectTaskMaterialSummary::getTaskId, taskId)
                 .and(w -> w.isNull(CostProjectTaskMaterialSummary::getMaterialOrderNum)
                         .or()
-                        .ge(CostProjectTaskMaterialSummary::getMaterialOrderNum, 0))
+                        .gt(CostProjectTaskMaterialSummary::getMaterialOrderNum, 0))
                 .orderByAsc(CostProjectTaskMaterialSummary::getMaterialOrderNum);
 
         List<CostProjectTaskMaterialSummary> summaryList = this.list(queryWrapper);

+ 22 - 0
assistMg/src/main/java/com/hotent/project/model/CostProjectTaskMaterialSummaryDetail.java

@@ -111,5 +111,27 @@ public class CostProjectTaskMaterialSummaryDetail extends BaseModel<CostProjectT
     @JsonProperty("updateBy")
     private String updateBy;
 
+
+    @ApiModelProperty(value = "备注")
+    @TableField("remark")
+    @JsonProperty("remark")
+    private String remark;
+
+    @ApiModelProperty(value = "保管期限")
+    @TableField("retention_period")
+    @JsonProperty("retentionPeriod")
+    private String retentionPeriod;
+
+    @ApiModelProperty(value = "卷宗号")
+    @TableField("archive_no")
+    @JsonProperty("archiveNo")
+    private String archiveNo;
+
+    @ApiModelProperty(value = "检查人")
+    @TableField("inspector")
+    @JsonProperty("inspector")
+    private String inspector;
+
+
 }
 

+ 180 - 79
assistMg/src/main/java/com/hotent/project/service/ArchiveTest.java

@@ -9,6 +9,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 卷宗生成测试类(使用Aspose实现多格式合并)
@@ -17,18 +18,18 @@ import java.util.List;
  * 页码规则:
  * - 封面:无页码
  * - 目录:无页码
- * - 正文:有页码,页眉全局连续(001、002...),页脚按资料类别重置(第 1 页 / 共 N 页)
+ * - 正文:有页码,页眉全局连续(001、002...),页脚按文件重置(第 1 页 / 共 N 页)
  * - 封底:无页码
  */
 public class ArchiveTest {
 
     /**
-     * 文件边界信息(用于追踪每个资料类别对应的节范围)
+     * 文件边界信息(用于追踪每个文件对应的节范围)
      */
     private static class FileRange {
-        int startSectionIndex;  // 资料类别开始的节索引
-        int endSectionIndex;    // 资料类别结束的节索引
-        String fileName;        // 资料类别名称
+        int startSectionIndex;  // 文件开始的节索引
+        int endSectionIndex;    // 文件结束的节索引
+        String fileName;        // 文件名称
 
         FileRange(int startSectionIndex, String fileName) {
             this.startSectionIndex = startSectionIndex;
@@ -51,22 +52,32 @@ public class ArchiveTest {
         catalogFiles.add(basePath + "卷内目录20251225133017203.docx");
 
         // 正文文件(支持Word、Excel、PDF混合)
-        // 注意:使用 ##SUMMARY_BOUNDARY## 分隔不同的资料类别
+        // 注意:使用 ##FILE_BOUNDARY## 分隔不同的文件
         List<String> contentFiles = new ArrayList<>();
 
-        // 资料类别1:成本监审表系列
+        // 文件1:政府定价成本监审结论报告
+        contentFiles.add(basePath + "政府定价成本监审结论报告20260202114449119.docx");
+        contentFiles.add("##FILE_BOUNDARY##");  // 文件分隔符
+
+        // 文件2:成本监审表(封面)
         contentFiles.add(basePath + "小店区-幼儿教育成本监审表(封面)_176664431285620251225143152602.xlsx");
+        contentFiles.add("##FILE_BOUNDARY##");  // 文件分隔符
+
+        // 文件3:成本监审表(固定资产)
         contentFiles.add(basePath + "小店区-幼儿教育成本监审表(固定资产) _176664457050920251225143610719.xlsx");
+        contentFiles.add("##FILE_BOUNDARY##");  // 文件分隔符
+
+        // 文件4:报送资料文件
         contentFiles.add(basePath + "报送资料文件20251225143055127.docx");
-        contentFiles.add("##SUMMARY_BOUNDARY##");  // 类别分隔符
+        contentFiles.add("##FILE_BOUNDARY##");  // 文件分隔符
 
-        // 资料类别2:提取资料登记表
+        // 文件5:提取资料登记表
         contentFiles.add(basePath + "成本监审提取资料登记表20251225154211987.docx");
-        contentFiles.add("##SUMMARY_BOUNDARY##");  // 类别分隔符
+        contentFiles.add("##FILE_BOUNDARY##");  // 文件分隔符
 
-        // 资料类别3:报送资料
+        // 文件6:报送资料文件
         contentFiles.add(basePath + "报送资料文件20251225143050824.docx");
-        // 最后一个类别不需要分隔符
+        // 最后一个文件不需要分隔符
 
         // 封底文件
         List<String> backCoverFiles = new ArrayList<>();
@@ -127,30 +138,30 @@ public class ArchiveTest {
         // 记录正文开始的节索引
         int contentStartSectionIndex = mainDoc.getSections().getCount();
 
-        // 3. 合并正文(有页码节)- 追踪每个资料类别的节范围
+        // 3. 合并正文(有页码节)- 追踪每个文件的节范围
         List<FileRange> contentFileRanges = new ArrayList<>();
         if (contentFiles != null && !contentFiles.isEmpty()) {
             System.out.println("\n3. 合并正文(有页码)...");
 
-            int summaryStartSection = -1;
-            int summaryIndex = 1;
-            String currentSummaryName = "资料类别" + summaryIndex;
+            int fileStartSection = -1;
+            int fileIndex = 1;
+            String currentFileName = "文件" + fileIndex;
 
             for (String filePath : contentFiles) {
                 // 检查是否是分隔符
-                if ("##SUMMARY_BOUNDARY##".equals(filePath)) {
-                    // 遇到分隔符,保存当前 summary 的范围
-                    if (summaryStartSection >= 0) {
-                        int summaryEndSection = mainDoc.getSections().getCount() - 1;
-                        FileRange fileRange = new FileRange(summaryStartSection, currentSummaryName);
-                        fileRange.endSectionIndex = summaryEndSection;
+                if ("##FILE_BOUNDARY##".equals(filePath)) {
+                    // 遇到分隔符,保存当前文件的范围
+                    if (fileStartSection >= 0) {
+                        int fileEndSection = mainDoc.getSections().getCount() - 1;
+                        FileRange fileRange = new FileRange(fileStartSection, currentFileName);
+                        fileRange.endSectionIndex = fileEndSection;
                         contentFileRanges.add(fileRange);
-                        System.out.println("   ✓ 资料类别 " + summaryIndex + " 完成,节范围:" + summaryStartSection + "-" + summaryEndSection);
+                        System.out.println("   ✓ 文件 " + fileIndex + " 完成,节范围:" + fileStartSection + "-" + fileEndSection);
                     }
-                    // 重置,准备下一个 summary
-                    summaryStartSection = -1;
-                    summaryIndex++;
-                    currentSummaryName = "资料类别" + summaryIndex;
+                    // 重置,准备下一个文件
+                    fileStartSection = -1;
+                    fileIndex++;
+                    currentFileName = "文件" + fileIndex;
                     continue;
                 }
 
@@ -158,22 +169,22 @@ public class ArchiveTest {
                 if (filePath != null && new File(filePath).exists()) {
                     System.out.println("   ✓ " + extractFileName(filePath));
 
-                    // 如果是当前 summary 的第一个文件,记录起始节索引
-                    if (summaryStartSection < 0) {
-                        summaryStartSection = mainDoc.getSections().getCount();
+                    // 如果是当前文件的第一个节,记录起始节索引
+                    if (fileStartSection < 0) {
+                        fileStartSection = mainDoc.getSections().getCount();
                     }
 
                     isFirst = appendDocument(mainDoc, filePath, isFirst, SectionStart.NEW_PAGE);
                 }
             }
 
-            // 处理最后一个 summary(如果没有遇到分隔符)
-            if (summaryStartSection >= 0) {
-                int summaryEndSection = mainDoc.getSections().getCount() - 1;
-                FileRange fileRange = new FileRange(summaryStartSection, currentSummaryName);
-                fileRange.endSectionIndex = summaryEndSection;
+            // 处理最后一个文件(如果没有遇到分隔符)
+            if (fileStartSection >= 0) {
+                int fileEndSection = mainDoc.getSections().getCount() - 1;
+                FileRange fileRange = new FileRange(fileStartSection, currentFileName);
+                fileRange.endSectionIndex = fileEndSection;
                 contentFileRanges.add(fileRange);
-                System.out.println("   ✓ 资料类别 " + summaryIndex + " 完成,节范围:" + summaryStartSection + "-" + summaryEndSection);
+                System.out.println("   ✓ 文件 " + fileIndex + " 完成,节范围:" + fileStartSection + "-" + fileEndSection);
             }
         }
 
@@ -191,10 +202,14 @@ public class ArchiveTest {
             }
         }
 
-        // 5. 设置页码(页眉全局连续,页脚按资料类别重置
-        System.out.println("\n5. 设置页码(每个资料类别独立编页码)...");
+        // 5. 设置页码(每个文件独立编页码
+        System.out.println("\n5. 设置页码(每个文件独立编页码)...");
         setupPageNumbersPerFile(mainDoc, contentFileRanges);
 
+        // 6. 在每页右上角添加全局页码(不使用页眉)
+        System.out.println("\n6. 在每页右上角添加全局页码...");
+        addPageNumbersToTopRight(mainDoc, contentFileRanges);
+
         // 保存文档
         File outputFile = new File(outputPath);
         if (outputFile.getParentFile() != null && !outputFile.getParentFile().exists()) {
@@ -537,10 +552,10 @@ public class ArchiveTest {
     }
 
     /**
-     * 设置页码(页眉全局连续,页脚按资料类别重置)
+     * 设置页码(页眉全局连续,页脚按文件重置)
      *
      * @param doc               文档
-     * @param contentFileRanges 正文资料类别的节范围列表
+     * @param contentFileRanges 正文文件的节范围列表
      */
     private static void setupPageNumbersPerFile(Document doc, List<FileRange> contentFileRanges) throws Exception {
         if (contentFileRanges == null || contentFileRanges.isEmpty()) {
@@ -585,12 +600,12 @@ public class ArchiveTest {
         }
         System.out.println("   正文全局起始页码:" + contentGlobalStartPage);
 
-        // 计算每个资料类别的页数和起始页码(全局)
+        // 计算每个文件的页数和起始页码(全局)
         for (FileRange fileRange : contentFileRanges) {
-            int summaryPageCount = calculateFilePages(doc, collector, fileRange.startSectionIndex, fileRange.endSectionIndex);
-            System.out.println("   资料类别 " + fileRange.fileName + " 页数: " + summaryPageCount);
+            int filePageCount = calculateFilePages(doc, collector, fileRange.startSectionIndex, fileRange.endSectionIndex);
+            System.out.println("   文件 " + fileRange.fileName + " 页数: " + filePageCount);
 
-            // 获取该资料类别的全局起始页码
+            // 获取该文件的全局起始页码
             Section firstSection = sections.get(fileRange.startSectionIndex);
             Node firstNode = firstSection.getBody().getFirstChild();
             int globalStartPage = 1;
@@ -598,7 +613,7 @@ public class ArchiveTest {
                 globalStartPage = collector.getStartPageIndex(firstNode);
             }
 
-            // 为这个资料类别的所有节设置页码
+            // 为这个文件的所有节设置页码
             for (int i = fileRange.startSectionIndex; i <= fileRange.endSectionIndex && i < sections.getCount(); i++) {
                 Section section = sections.get(i);
                 HeaderFooterCollection headerFooters = section.getHeadersFooters();
@@ -625,11 +640,15 @@ public class ArchiveTest {
                     currentGlobalPage = collector.getStartPageIndex(currentSectionFirstNode);
                 }
 
+                // 调试日志
+                System.out.println(String.format("   节 %d: 全局页码=%d, 文件起始页码=%d",
+                    i, currentGlobalPage, globalStartPage));
+
                 // 计算正文内的相对页码(从1开始)
                 int globalPageNum = currentGlobalPage - contentGlobalStartPage + 1;
 
-                // 关键:只在每个文件的第一个节重置页码为1
-                // 同一文件的其他节不重置,继续递增
+                // 关键:在每个文件的第一个节重置页码为1
+                // 这样 PAGE 字段会在每个文件内从1开始递增
                 if (i == fileRange.startSectionIndex) {
                     section.getPageSetup().setRestartPageNumbering(true);
                     section.getPageSetup().setPageStartingNumber(1);
@@ -637,35 +656,8 @@ public class ArchiveTest {
                     section.getPageSetup().setRestartPageNumbering(false);
                 }
 
-                // ========== 创建页眉(右上角编号 001、002、003...全局连续)==========
-                HeaderFooter header = new HeaderFooter(doc, HeaderFooterType.HEADER_PRIMARY);
-                headerFooters.add(header);
-
-                // 创建页眉段落(右对齐)
-                Paragraph headerPara = new Paragraph(doc);
-                headerPara.getParagraphFormat().setAlignment(ParagraphAlignment.RIGHT);
-                headerPara.getParagraphFormat().setRightIndent(20);
-                headerPara.getParagraphFormat().getBorders().getBottom().setLineStyle(LineStyle.NONE);
-
-                header.appendChild(headerPara);
-
-                // 使用DocumentBuilder插入带格式的页码字段
-                DocumentBuilder builder = new DocumentBuilder(doc);
-                builder.moveTo(headerPara);
-
-                // 设置字体(加粗)
-                builder.getFont().setName("宋体");
-                builder.getFont().setSize(12);
-                builder.getFont().setBold(true);
-
-                // 插入全局页码(使用静态文本)
-                // 因为PAGE字段现在是每文件独立的,无法用于全局连续编号
-                String pageNumStr = String.format("%03d", globalPageNum);
-                builder.write(pageNumStr);
-
-                section.getPageSetup().setHeaderDistance(20);
-
                 // ========== 创建页脚(第 X 页 / 共 Y 页)==========
+                DocumentBuilder builder = new DocumentBuilder(doc);
                 HeaderFooter footer = new HeaderFooter(doc, HeaderFooterType.FOOTER_PRIMARY);
                 headerFooters.add(footer);
 
@@ -687,15 +679,14 @@ public class ArchiveTest {
                 // 添加"第 "
                 builder.write("第 ");
 
-                // 页脚使用 PAGE 字段(动态递增)
-                // 因为每个文件第一个节重置为1,所以PAGE字段会自动显示正确的相对页码
+                // 页脚使用 PAGE 字段(因为每个文件第一个节重置为1,所以会自动显示文件内页码)
                 builder.insertField("PAGE", "1");
 
                 // 添加" 页 / 共 "
                 builder.write(" 页 / 共 ");
 
                 // 插入总页数(固定值)
-                builder.write(String.valueOf(summaryPageCount));
+                builder.write(String.valueOf(filePageCount));
 
                 // 添加" 页"
                 builder.write(" 页");
@@ -707,7 +698,7 @@ public class ArchiveTest {
     }
 
     /**
-     * 计算指定节范围的页数(用于单个资料类别
+     * 计算指定节范围的页数(用于单个文件
      */
     private static int calculateFilePages(Document doc, LayoutCollector collector, int startSectionIndex, int endSectionIndex) throws Exception {
         SectionCollection sections = doc.getSections();
@@ -752,4 +743,114 @@ public class ArchiveTest {
         int lastSlash = Math.max(url.lastIndexOf('/'), url.lastIndexOf('\\'));
         return lastSlash >= 0 ? url.substring(lastSlash + 1) : url;
     }
+
+    /**
+     * 在每页右上角添加全局页码(不使用页眉)
+     *
+     * @param doc               文档
+     * @param contentFileRanges 正文文件的节范围列表
+     */
+    private static void addPageNumbersToTopRight(Document doc, List<FileRange> contentFileRanges) throws Exception {
+        if (contentFileRanges == null || contentFileRanges.isEmpty()) {
+            System.out.println("   [警告] 没有正文文件,跳过右上角页码设置");
+            return;
+        }
+
+        // 更新文档布局
+        doc.updatePageLayout();
+        LayoutCollector collector = new LayoutCollector(doc);
+
+        // 计算正文起始页码(全局)
+        int contentGlobalStartPage = Integer.MAX_VALUE;
+        FileRange firstFileRange = contentFileRanges.get(0);
+        Section firstSection = doc.getSections().get(firstFileRange.startSectionIndex);
+        Node firstNode = firstSection.getBody().getFirstChild();
+        if (firstNode != null) {
+            contentGlobalStartPage = collector.getStartPageIndex(firstNode);
+        }
+
+        System.out.println("   正文全局起始页码:" + contentGlobalStartPage);
+
+        // 遍历每个文件的每个节
+        for (FileRange fileRange : contentFileRanges) {
+            System.out.println("   处理文件:" + fileRange.fileName);
+
+            for (int sectionIdx = fileRange.startSectionIndex; sectionIdx <= fileRange.endSectionIndex && sectionIdx < doc.getSections().getCount(); sectionIdx++) {
+                Section section = doc.getSections().get(sectionIdx);
+                Body body = section.getBody();
+
+                // 获取该节的所有段落
+                NodeCollection paragraphs = body.getChildNodes(NodeType.PARAGRAPH, true);
+                if (paragraphs.getCount() == 0) {
+                    continue;
+                }
+
+                // 按页分组段落
+                Map<Integer, List<Paragraph>> pageGroups = new java.util.HashMap<>();
+                for (int i = 0; i < paragraphs.getCount(); i++) {
+                    Paragraph para = (Paragraph) paragraphs.get(i);
+                    int pageNum = collector.getStartPageIndex(para);
+                    pageGroups.computeIfAbsent(pageNum, k -> new java.util.ArrayList<>()).add(para);
+                }
+
+                // 为每一页添加右上角页码
+                for (Map.Entry<Integer, List<Paragraph>> entry : pageGroups.entrySet()) {
+                    int globalPage = entry.getKey();
+                    List<Paragraph> pageParagraphs = entry.getValue();
+
+                    if (pageParagraphs.isEmpty()) {
+                        continue;
+                    }
+
+                    // 计算正文内的相对页码
+                    int globalPageNum = globalPage - contentGlobalStartPage + 1;
+                    String pageNumStr = String.format("%03d", globalPageNum);
+
+                    // 在该页的第一个段落中插入浮动文本框
+                    Paragraph firstPara = pageParagraphs.get(0);
+
+                    // 创建文本框(浮动定位)
+                    Shape textBox = new Shape(doc, ShapeType.TEXT_BOX);
+                    textBox.setWidth(60);
+                    textBox.setHeight(20);
+
+                    // 设置为浮动定位(相对于页边距,即版心)
+                    textBox.setWrapType(WrapType.NONE);
+                    textBox.setRelativeHorizontalPosition(RelativeHorizontalPosition.MARGIN);  // 相对于页边距
+                    textBox.setRelativeVerticalPosition(RelativeVerticalPosition.MARGIN);  // 相对于页边距
+                    textBox.setHorizontalAlignment(HorizontalAlignment.RIGHT);  // 右对齐
+                    textBox.setTop(0);  // 距离版心顶部0磅
+                    textBox.setLeft(0);  // 由于右对齐,这个值会被忽略
+
+                    // 设置文本框样式
+                    textBox.getStroke().setVisible(false);  // 无边框
+                    textBox.getFill().setColor(java.awt.Color.WHITE);  // 白色背景
+
+                    // 在文本框中添加段落和文本
+                    Paragraph textBoxPara = new Paragraph(doc);
+                    textBoxPara.getParagraphFormat().setAlignment(ParagraphAlignment.RIGHT);
+
+                    Run run = new Run(doc, pageNumStr);
+                    run.getFont().setName("宋体");
+                    run.getFont().setSize(12);
+                    run.getFont().setBold(true);
+                    run.getFont().setColor(java.awt.Color.BLACK);
+
+                    textBoxPara.appendChild(run);
+                    textBox.appendChild(textBoxPara);
+
+                    // 将文本框插入到段落的开头(作为段落的第一个子节点)
+                    if (firstPara.getChildNodes().getCount() > 0) {
+                        firstPara.insertBefore(textBox, firstPara.getFirstChild());
+                    } else {
+                        firstPara.appendChild(textBox);
+                    }
+
+                    System.out.println(String.format("     页 %d: 添加页码 %s", globalPage, pageNumStr));
+                }
+            }
+        }
+
+        System.out.println("   右上角页码添加完成");
+    }
 }

+ 120 - 37
assistMg/src/main/java/com/hotent/project/service/AsposeArchiveService.java

@@ -159,6 +159,10 @@ public class AsposeArchiveService {
         }
         setupPageNumbersPerFile(mainDoc, contentFileRanges);
 
+        // 6. 在每页右上角添加全局页码(不使用页眉)
+        logger.info("6. 在每页右上角添加全局页码...");
+        addPageNumbersToTopRight(mainDoc, contentFileRanges);
+
         // 保存文档
         File outputFile = new File(outputPath);
         if (outputFile.getParentFile() != null && !outputFile.getParentFile().exists()) {
@@ -693,44 +697,17 @@ public class AsposeArchiveService {
                 // 计算正文内的相对页码(从1开始)
                 int globalPageNum = currentGlobalPage - contentGlobalStartPage + 1;
 
-                // 关键:只在正文的第一个节重置页码为1,其他节不重置,保持全局连续
-                if (i == contentFileRanges.get(0).startSectionIndex) {
-                    // 只在正文第一个节重置页码为1
+                // 关键:在每个文件的第一个节重置页码为1
+                // 这样 PAGE 字段会在每个文件内从1开始递增
+                if (i == fileRange.startSectionIndex) {
                     section.getPageSetup().setRestartPageNumbering(true);
                     section.getPageSetup().setPageStartingNumber(1);
                 } else {
-                    // 其他所有节都不重置,保持全局连续递增
                     section.getPageSetup().setRestartPageNumbering(false);
                 }
 
-                // ========== 创建页眉(右上角编号 001、002、003...全局连续)==========
-                HeaderFooter header = new HeaderFooter(doc, HeaderFooterType.HEADER_PRIMARY);
-                headerFooters.add(header);
-
-                // 创建页眉段落(右对齐)
-                Paragraph headerPara = new Paragraph(doc);
-                headerPara.getParagraphFormat().setAlignment(ParagraphAlignment.RIGHT);
-                headerPara.getParagraphFormat().setRightIndent(20);
-                headerPara.getParagraphFormat().getBorders().getBottom().setLineStyle(LineStyle.NONE);
-
-                header.appendChild(headerPara);
-
-                // 使用DocumentBuilder插入带格式的页码字段
+                // ========== 只创建页脚(不创建页眉)==========
                 DocumentBuilder builder = new DocumentBuilder(doc);
-                builder.moveTo(headerPara);
-
-                // 设置字体(加粗)
-                builder.getFont().setName("宋体");
-                builder.getFont().setSize(12);
-                builder.getFont().setBold(true);
-
-                // 修复:使用动态PAGE字段,配合格式化显示为001、002、003...
-                // 插入PAGE字段,并使用\# "000"格式化为三位数
-                builder.insertField("PAGE \\# \"000\"", "001");
-
-                section.getPageSetup().setHeaderDistance(20);
-
-                // ========== 创建页脚(第 X 页 / 共 Y 页)- 每个文件独立计算 ==========
                 HeaderFooter footer = new HeaderFooter(doc, HeaderFooterType.FOOTER_PRIMARY);
                 headerFooters.add(footer);
 
@@ -749,15 +726,11 @@ public class AsposeArchiveService {
                 builder.getFont().setSize(10.5);
                 builder.getFont().setBold(false);
 
-                // 计算当前文件内的相对页码(用于页脚显示)
-                // 获取当前节在当前文件中的相对页码
-                int fileRelativePageNum = currentGlobalPage - globalStartPage + 1;
-
                 // 添加"第 "
                 builder.write("第 ");
 
-                // 页脚显示当前文件内的相对页码(静态文本
-                builder.write(String.valueOf(fileRelativePageNum));
+                // 页脚使用 PAGE 字段(因为每个文件第一个节重置为1,所以会自动显示文件内页码)
+                builder.insertField("PAGE", "1");
 
                 // 添加" 页 / 共 "
                 builder.write(" 页 / 共 ");
@@ -974,4 +947,114 @@ public class AsposeArchiveService {
         logger.warn("   创建错误提示文档:{}", errorMessage);
         return doc;
     }
+
+    /**
+     * 在每页右上角添加全局页码(不使用页眉)
+     *
+     * @param doc               文档
+     * @param contentFileRanges 正文文件的节范围列表
+     */
+    private void addPageNumbersToTopRight(Document doc, List<FileRange> contentFileRanges) throws Exception {
+        if (contentFileRanges == null || contentFileRanges.isEmpty()) {
+            logger.warn("   [警告] 没有正文文件,跳过右上角页码设置");
+            return;
+        }
+
+        // 更新文档布局
+        doc.updatePageLayout();
+        LayoutCollector collector = new LayoutCollector(doc);
+
+        // 计算正文起始页码(全局)
+        int contentGlobalStartPage = Integer.MAX_VALUE;
+        FileRange firstFileRange = contentFileRanges.get(0);
+        Section firstSection = doc.getSections().get(firstFileRange.startSectionIndex);
+        Node firstNode = firstSection.getBody().getFirstChild();
+        if (firstNode != null) {
+            contentGlobalStartPage = collector.getStartPageIndex(firstNode);
+        }
+
+        logger.info("   正文全局起始页码:{}", contentGlobalStartPage);
+
+        // 遍历每个文件的每个节
+        for (FileRange fileRange : contentFileRanges) {
+            logger.info("   处理文件:{}", fileRange.fileName);
+
+            for (int sectionIdx = fileRange.startSectionIndex; sectionIdx <= fileRange.endSectionIndex && sectionIdx < doc.getSections().getCount(); sectionIdx++) {
+                Section section = doc.getSections().get(sectionIdx);
+                Body body = section.getBody();
+
+                // 获取该节的所有段落
+                NodeCollection paragraphs = body.getChildNodes(NodeType.PARAGRAPH, true);
+                if (paragraphs.getCount() == 0) {
+                    continue;
+                }
+
+                // 按页分组段落
+                java.util.Map<Integer, java.util.List<Paragraph>> pageGroups = new java.util.HashMap<>();
+                for (int i = 0; i < paragraphs.getCount(); i++) {
+                    Paragraph para = (Paragraph) paragraphs.get(i);
+                    int pageNum = collector.getStartPageIndex(para);
+                    pageGroups.computeIfAbsent(pageNum, k -> new java.util.ArrayList<>()).add(para);
+                }
+
+                // 为每一页添加右上角页码
+                for (java.util.Map.Entry<Integer, java.util.List<Paragraph>> entry : pageGroups.entrySet()) {
+                    int globalPage = entry.getKey();
+                    java.util.List<Paragraph> pageParagraphs = entry.getValue();
+
+                    if (pageParagraphs.isEmpty()) {
+                        continue;
+                    }
+
+                    // 计算正文内的相对页码
+                    int globalPageNum = globalPage - contentGlobalStartPage + 1;
+                    String pageNumStr = String.format("%03d", globalPageNum);
+
+                    // 在该页的第一个段落中插入浮动文本框
+                    Paragraph firstPara = pageParagraphs.get(0);
+
+                    // 创建文本框(浮动定位)
+                    Shape textBox = new Shape(doc, ShapeType.TEXT_BOX);
+                    textBox.setWidth(60);
+                    textBox.setHeight(20);
+
+                    // 设置为浮动定位(相对于页边距,即版心)
+                    textBox.setWrapType(WrapType.NONE);
+                    textBox.setRelativeHorizontalPosition(RelativeHorizontalPosition.MARGIN);  // 相对于页边距
+                    textBox.setRelativeVerticalPosition(RelativeVerticalPosition.MARGIN);  // 相对于页边距
+                    textBox.setHorizontalAlignment(HorizontalAlignment.RIGHT);  // 右对齐
+                    textBox.setTop(0);  // 距离版心顶部0磅
+                    textBox.setLeft(0);  // 由于右对齐,这个值会被忽略
+
+                    // 设置文本框样式
+                    textBox.getStroke().setVisible(false);  // 无边框
+                    textBox.getFill().setColor(java.awt.Color.WHITE);  // 白色背景
+
+                    // 在文本框中添加段落和文本
+                    Paragraph textBoxPara = new Paragraph(doc);
+                    textBoxPara.getParagraphFormat().setAlignment(ParagraphAlignment.RIGHT);
+
+                    Run run = new Run(doc, pageNumStr);
+                    run.getFont().setName("宋体");
+                    run.getFont().setSize(12);
+                    run.getFont().setBold(true);
+                    run.getFont().setColor(java.awt.Color.BLACK);
+
+                    textBoxPara.appendChild(run);
+                    textBox.appendChild(textBoxPara);
+
+                    // 将文本框插入到段落的开头(作为段落的第一个子节点)
+                    if (firstPara.getChildNodes().getCount() > 0) {
+                        firstPara.insertBefore(textBox, firstPara.getFirstChild());
+                    } else {
+                        firstPara.appendChild(textBox);
+                    }
+
+                    logger.debug("     页 {}: 添加页码 {}", globalPage, pageNumStr);
+                }
+            }
+        }
+
+        logger.info("   右上角页码添加完成");
+    }
 }

+ 15 - 0
assistMg/src/main/java/com/hotent/project/service/AsyncMaterialSummaryService.java

@@ -1204,20 +1204,30 @@ public class AsyncMaterialSummaryService {
             );
             if (existingDetail != null) {
                 // 更新已有明细
+                existingDetail.setRemark(req.getRemark());
                 existingDetail.setAttachmentUrl(outputUrl);
                 existingDetail.setGenerateTime(java.time.LocalDateTime.now());
+                // 更新新增字段
+                existingDetail.setRetentionPeriod(req.getRetentionPeriod());
+                existingDetail.setArchiveNo(req.getArchiveNo());
+                existingDetail.setInspector(req.getChecker());
                 costProjectTaskMaterialSummaryDetailManager.updateById(existingDetail);
             } else {
                 // 创建新明细
                 CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
                 detail.setMasterId(masterId);
                 detail.setTaskId(req.getTaskId());
+                detail.setRemark(req.getRemark());
                 detail.setDocumentName(documentName);
                 detail.setFileSource("系统生成电子文书");
                 detail.setAttachmentUrl(outputUrl);
                 detail.setOrderNum(1);
                 detail.setGenerateTime(java.time.LocalDateTime.now());
                 detail.setIsDeleted("0");
+                // 设置新增字段
+                detail.setRetentionPeriod(req.getRetentionPeriod());
+                detail.setArchiveNo(req.getArchiveNo());
+                detail.setInspector(req.getChecker());
                 costProjectTaskMaterialSummaryDetailManager.save(detail);
             }
         } else {
@@ -1247,6 +1257,11 @@ public class AsyncMaterialSummaryService {
             detail.setOrderNum(1);
             detail.setGenerateTime(java.time.LocalDateTime.now());
             detail.setIsDeleted("0");
+            // 设置新增字段
+            detail.setRemark(req.getRemark());
+            detail.setRetentionPeriod(req.getRetentionPeriod());
+            detail.setArchiveNo(req.getArchiveNo());
+            detail.setInspector(req.getChecker());
             costProjectTaskMaterialSummaryDetailManager.save(detail);
         }