徐展城 6 روز پیش
والد
کامیت
c37a4e18cc
2فایلهای تغییر یافته به همراه247 افزوده شده و 54 حذف شده
  1. 246 53
      src/main/java/com/grapecity/controller/GrapeCityController.java
  2. 1 1
      src/main/resources/logback-spring.xml

+ 246 - 53
src/main/java/com/grapecity/controller/GrapeCityController.java

@@ -192,7 +192,7 @@ public class GrapeCityController {
             throw new IllegalArgumentException("请求参数不能为空");
         }
 
-        logger.debug("收到 pdf 请求:{}", request);
+//        logger.debug("收到 pdf 请求:{}", request);
 
         // 转换 templateBytes
         byte[] templateBytes = convertToByteArray(request.get("templateBytes"), "templateBytes");
@@ -216,8 +216,31 @@ public class GrapeCityController {
             workbook = new Workbook(workbookOptions);
             inputStream = new ByteArrayInputStream(templateBytes);
             workbook.open(inputStream, OpenFileFormat.Sjs);
+
+            // 先处理续页(此时模板还未展开,toJson 捕获的是模板结构)
+            boolean continuePage = false;
+            String copyConfigJson = null;
+            try {
+                continuePage = request.get("continuePage") != null && Boolean.parseBoolean(request.get("continuePage").toString());
+                copyConfigJson = request.get("copyConfig") != null
+                        ? request.get("copyConfig").toString()
+                        : null;
+                List<String> newColCodes = new ArrayList<>();
+                List<String> existingColCodes = extractStringList(request.get("existingColCodes"));
+                logger.info("[fullPdf] continuePage={}, copyConfigJson={}, dataKeys={}, sheetCount={}",
+                        continuePage, copyConfigJson != null, data != null ? data.size() : 0,
+                        workbook.getWorksheets().getCount());
+                if (continuePage && copyConfigJson != null) {
+                    workbook = handleContinuationPages(workbook, copyConfigJson, existingColCodes, newColCodes, data);
+                    logger.info("[fullPdf] handleContinuationPages 完成, sheetCount={}", workbook.getWorksheets().getCount());
+                }
+            } catch (Exception e) {
+                logger.error("处理分页异常:{}", e.getMessage());
+            }
+
             for (int i = 0; i < workbook.getWorksheets().getCount(); i++) {
                 IWorksheet worksheet = workbook.getWorksheets().get(i);
+                logger.info("[fullPdf] 处理 sheet[{}]: {}", i, worksheet.getName());
                 worksheet.getPageSetup().setPrintHeadings(false);
                 worksheet.getPageSetup().setPaperSize(PaperSize.A4);
                 worksheet.getPageSetup().setLeftMargin(7); // 左边距
@@ -226,9 +249,67 @@ public class GrapeCityController {
                 worksheet.getUsedRange().setShrinkToFit(true);
                 // 在 setDataSource 前收集表格第一行数据行的合并单元格规则(setDataSource 会将已有合并拆开)
                 Map<ITable, List<int[]>> tableMergeInfo = collectTableMergeInfo(worksheet);
-                worksheet.setDataSource(new JsonDataSource(JSON.toJSONString(data)));
+
+                boolean isContinuationSheet = worksheet.getName().contains(" (");
+
+                if (!isContinuationSheet) {
+                    worksheet.setDataSource(new JsonDataSource(JSON.toJSONString(data)));
+                }
+
+                fillCellsByBindingPath(worksheet, data);
+
                 // 数据源绑定后,将第一行数据行的样式、行高、合并规则复制到所有自动扩展的数据行
-                copyTableRowStyles(worksheet, tableMergeInfo);
+                if (!isContinuationSheet) {
+                    copyTableRowStyles(worksheet, tableMergeInfo);
+                }
+
+                // 续页填写值后验证数据存在
+                if (isContinuationSheet) {
+                    int nonEmptyCount = 0;
+                    for (int r = 0; r < worksheet.getRowCount() && nonEmptyCount < 5; r++) {
+                        for (int c = 0; c < worksheet.getColumnCount() && nonEmptyCount < 5; c++) {
+                            Object val = worksheet.getRange(r, c).getValue();
+                            if (val != null && !val.toString().isEmpty()) {
+                                nonEmptyCount++;
+                            }
+                        }
+                    }
+                    logger.info("[fullPdf] 续页 {} 数据校验: 前5个非空值={}", worksheet.getName(), nonEmptyCount);
+                }
+
+                // hidden 检查:数据填充后判断原始续页是否为空,空则隐藏
+                if (continuePage && copyConfigJson != null && !isContinuationSheet) {
+                    try {
+                        JSONObject cc = JSON.parseObject(copyConfigJson);
+                        String ccSheetName = cc.getString("sheetName");
+                        Boolean ccHidden = cc.getBoolean("hidden");
+                        if (Boolean.TRUE.equals(ccHidden) && ccSheetName != null && ccSheetName.equals(worksheet.getName())) {
+                            JSONObject cr = cc.getJSONObject("copyRange");
+                            if (cr != null) {
+                                String[] tls = cr.getString("topLeft").split(",");
+                                String[] trs = cr.getString("topRight").split(",");
+                                int ccCol = Integer.parseInt(tls[0]);
+                                int ccRow = Integer.parseInt(tls[1]);
+                                int ccColCount = Integer.parseInt(trs[0]) - ccCol;
+                                boolean isEmpty = true;
+                                for (int k = 0; k <= ccColCount; k++) {
+                                    Object cv = worksheet.getRange(ccRow, ccCol + k).getValue();
+                                    if (cv != null && !cv.toString().isEmpty()) {
+                                        isEmpty = false;
+                                        break;
+                                    }
+                                }
+                                logger.info("[fullPdf] hidden 检查 sheet={}, isEmpty={}", worksheet.getName(), isEmpty);
+                                if (isEmpty) {
+                                    worksheet.setVisible(Visibility.Hidden);
+                                }
+                            }
+                        }
+                    } catch (Exception e) {
+                        logger.warn("[fullPdf] hidden 检查异常: {}", e.getMessage());
+                    }
+                }
+
                 if (!worksheet.getName().contains("封面") && !worksheet.getName().contains("注意")) {
                     worksheet.getPageSetup().setIsAutoFirstPageNumber(true);
                 }
@@ -239,20 +320,6 @@ public class GrapeCityController {
                 // 浮动图片
                 processFloatingImages(worksheet, data, fileBytes);
             }
-            try {
-                boolean continuePage = request.get("continuePage") != null && Boolean.parseBoolean(request.get("continuePage").toString());
-                String copyConfigJson = request.get("copyConfig") != null
-                        ? request.get("copyConfig").toString()
-                        : null;
-                List<String> newColCodes = new ArrayList<>();
-                List<String> existingColCodes = extractStringList(request.get("existingColCodes"));
-                if (continuePage && copyConfigJson != null) {
-                    workbook = handleContinuationPages(workbook, copyConfigJson, existingColCodes, newColCodes);
-                }
-            } catch (Exception e) {
-                logger.error("处理分页异常:{}", e.getMessage());
-            }
-
             PrintManager printManager = new PrintManager();
             //Workbook.FontsFolderPath = this.fontsFolderPath;
             Workbook.FontProvider = new IFontProvider() {
@@ -419,12 +486,18 @@ public class GrapeCityController {
      * copyConfigJson 和 existingColCodes 由 pressure2 传入,newColCodes 通过参数返回给调用方
      */
     private Workbook handleContinuationPages(Workbook workbook, String copyConfigJson,
-                                             List<String> existingColCodes, List<String> newColCodes) {
+                                             List<String> existingColCodes, List<String> newColCodes,
+                                             Map<String, Object> data) {
         try {
+            logger.info("[handleContinuationPages] 进入, sheetCount={}, dataKeys={}",
+                    workbook.getWorksheets().getCount(), data != null ? data.size() : 0);
             JSONObject copyConfig = JSON.parseObject(copyConfigJson);
 
             Boolean isContinuePage = copyConfig.getBoolean("isContinuePage");
-            if (!Boolean.TRUE.equals(isContinuePage)) return workbook;
+            if (!Boolean.TRUE.equals(isContinuePage)) {
+                logger.info("[handleContinuationPages] isContinuePage=false, 跳过");
+                return workbook;
+            }
 
             String sheetName = copyConfig.getString("sheetName");
             if (sheetName == null) return workbook;
@@ -440,9 +513,8 @@ public class GrapeCityController {
             int row = Integer.parseInt(topLeft[1]);
             int colCount = Integer.parseInt(topRight[0]) - col;
             int rowCount = Integer.parseInt(bottomLeft[1]) - row;
+            int rowsPerPage = rowCount + 1;
 
-            WorkbookOptions workbookOptions = new WorkbookOptions();
-            workbookOptions.setPixelBasedColumnWidth(true);
             IWorksheet targetSheet = null;
             for (int i = 0; i < workbook.getWorksheets().getCount(); i++) {
                 if (workbook.getWorksheets().get(i).getName().equals(sheetName)) {
@@ -452,42 +524,79 @@ public class GrapeCityController {
             }
             if (targetSheet == null) return workbook;
 
-            Boolean hidden = copyConfig.getBoolean("hidden");
-            if (Boolean.TRUE.equals(hidden)) {
-                boolean isEmpty = true;
-                for (int i = 0; i <= colCount; i++) {
-                    Object cellValue = targetSheet.getRange(row, col + i).getValue();
-                    if (cellValue != null && !cellValue.toString().isEmpty()) {
-                        isEmpty = false;
-                        break;
+            // 提前捕获模板 JSON(在 hidden 检查前,避免捕获隐藏状态),去掉 name/visible 防止 fromJson 冲突
+            String templateSheetJson = targetSheet.toJson();
+            JSONObject templateJsonObj = JSON.parseObject(templateSheetJson);
+            templateJsonObj.remove("name");
+            templateJsonObj.remove("visible");
+            String strippedJson = templateJsonObj.toJSONString();
+
+            // 扫描 copyRange 找出所有前缀组和最小索引 (copyMinIndex)
+            int copyMinIndex = Integer.MAX_VALUE;
+            Set<String> prefixes = new HashSet<>();
+            java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("^(.+)_(\\d+)$");
+            for (int i = 0; i <= colCount; i++) {
+                for (int j = 0; j <= rowCount; j++) {
+                    String bp = targetSheet.getRange(row + j, col + i).getBindingPath();
+                    if (bp != null) {
+                        java.util.regex.Matcher matcher = pattern.matcher(bp);
+                        if (matcher.find()) {
+                            prefixes.add(matcher.group(1) + "_");
+                            int num = Integer.parseInt(matcher.group(2));
+                            if (num < copyMinIndex) copyMinIndex = num;
+                        }
                     }
                 }
-                if (isEmpty) {
-                    targetSheet.setVisible(Visibility.Hidden);
+            }
+
+            // 后端同步前端 distributeArrayFieldToCells:将 prefix_1 中保存的全量数组拆成 prefix_1...prefix_N
+            int maxArrayLen = expandArrayFieldsForContinuation(data, prefixes);
+            logger.info("[handleContinuationPages] expandArrayFieldsForContinuation 完成, maxArrayLen={}", maxArrayLen);
+
+            // 兼容已经是 prefix_1...prefix_N 形式的数据
+            if (data != null && !data.isEmpty()) {
+                for (String prefix : prefixes) {
+                    int localMax = 0;
+                    for (String key : data.keySet()) {
+                        if (key.startsWith(prefix)) {
+                            try {
+                                int num = Integer.parseInt(key.substring(prefix.length()));
+                                if (num > localMax) localMax = num;
+                            } catch (NumberFormatException ignored) {}
+                        }
+                    }
+                    logger.info("[handleContinuationPages] prefix={}, 兼容扫描得到 maxIndex={}", prefix, localMax);
+                    if (localMax > maxArrayLen) maxArrayLen = localMax;
                 }
             }
 
-            List<String> effectiveExistingColCodes = existingColCodes != null ? existingColCodes : new ArrayList<>();
+            logger.info("[handleContinuationPages] copyMinIndex={}, rowsPerPage={}, maxArrayLen={}, prefixes={}",
+                    copyMinIndex, rowsPerPage, maxArrayLen, prefixes);
 
-            int sheetNum = 2;
+            List<String> effectiveExistingColCodes = existingColCodes != null ? existingColCodes : new ArrayList<>();
 
-            boolean isNotEmpty = false;
-            for (int i = 0; i <= colCount; i++) {
-                Object cellValue = targetSheet.getRange(row + rowCount, col + i).getValue();
-                if (cellValue != null && !cellValue.toString().isEmpty()) {
-                    isNotEmpty = true;
-                    break;
+            // 先固定总页数,再生成续页,避免生成过程中 workbook/sheet 状态影响循环判断
+            int totalPages = 1;
+            if (copyMinIndex < Integer.MAX_VALUE && maxArrayLen > 0) {
+                while (maxArrayLen > (copyMinIndex - 1) + totalPages * rowsPerPage) {
+                    logger.info("[handleContinuationPages] totalPages while 循环: totalPages={}, usedSoFar={}, maxArrayLen={}",
+                            totalPages, (copyMinIndex - 1) + totalPages * rowsPerPage, maxArrayLen);
+                    totalPages++;
                 }
             }
+            logger.info("[handleContinuationPages] totalPages={}, continuationPages={}, beforeLoopSheetCount={}",
+                    totalPages, Math.max(0, totalPages - 1), workbook.getWorksheets().getCount());
 
-            if (isNotEmpty) {
-                generateContinuedSheet(workbook, targetSheet, sheetName, sheetNum,
-                        col, row, colCount, rowCount, effectiveExistingColCodes, newColCodes);
-                sheetNum++;
+            for (int pageNo = 2; pageNo <= totalPages; pageNo++) {
+                logger.info("[handleContinuationPages] 生成续页 pageNo={}", pageNo);
+                try {
+                    generateContinuedSheet(workbook, strippedJson, sheetName, pageNo,
+                            col, row, colCount, rowCount, effectiveExistingColCodes, newColCodes, data);
+                } catch (Exception e) {
+                    logger.error("[handleContinuationPages] 生成续页失败 pageNo={}", pageNo, e);
+                }
             }
 
-            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-            workbook.save(outputStream, SaveFileFormat.Sjs);
             return workbook;
         } catch (Exception e) {
             logger.warn("续页处理失败,回退返回原始 SJS", e);
@@ -495,39 +604,103 @@ public class GrapeCityController {
         }
     }
 
+    private int expandArrayFieldsForContinuation(Map<String, Object> data, Set<String> prefixes) {
+        if (data == null || data.isEmpty() || prefixes == null || prefixes.isEmpty()) return 0;
+
+        int maxArrayLen = 0;
+        for (String prefix : prefixes) {
+            Object fullArrayObj = null;
+            for (int i = 0; i <= 1000; i++) {
+                Object candidate = data.get(prefix + i);
+                if (candidate instanceof List<?> list && !list.isEmpty()) {
+                    fullArrayObj = list;
+                    break;
+                }
+                if (candidate instanceof JSONArray arr && !arr.isEmpty()) {
+                    fullArrayObj = arr;
+                    break;
+                }
+            }
+
+            JSONArray fullArray = toJSONArray(fullArrayObj);
+            if (fullArray == null || fullArray.isEmpty()) {
+                continue;
+            }
+
+            maxArrayLen = Math.max(maxArrayLen, fullArray.size());
+            for (int i = 0; i < fullArray.size(); i++) {
+                data.put(prefix + (i + 1), fullArray.get(i));
+            }
+            logger.info("[expandArrayFieldsForContinuation] prefix={}, fullArrayLen={}", prefix, fullArray.size());
+        }
+        return maxArrayLen;
+    }
+
     /**
      * 生成续页工作表:复制 sheet 并调整绑定路径
      * 新字段编码通过 newColCodes 参数返回给调用方
      */
-    private void generateContinuedSheet(Workbook workbook, IWorksheet sourceSheet,
-                                        String sheetName, int sheetNum,
+    private void generateContinuedSheet(Workbook workbook, String templateJson,
+                                        String sheetName, int pageNo,
                                         int col, int row, int colCount, int rowCount,
-                                        List<String> existingColCodes, List<String> newColCodes) {
-        String sheetJson = sourceSheet.toJson();
+                                        List<String> existingColCodes, List<String> newColCodes,
+                                        Map<String, Object> data) {
+        logger.info("[generateContinuedSheet] 开始 pageNo={}, beforeAddSheetCount={}", pageNo, workbook.getWorksheets().getCount());
         IWorksheet newSheet = workbook.getWorksheets().add();
-        newSheet.fromJson(sheetJson);
-        String newSheetName = sheetName + " (" + sheetNum + ")";
+        logger.info("[generateContinuedSheet] add 后 sheetCount={}", workbook.getWorksheets().getCount());
+        newSheet.fromJson(templateJson);
+        logger.info("[generateContinuedSheet] fromJson 后 sheetCount={}", workbook.getWorksheets().getCount());
+        String newSheetName = sheetName + " (" + pageNo + ")";
         newSheet.setName(newSheetName);
 
+        // 解绑新 sheet 上的 table,防止后续 setDataSource 按全量数组重新展开
+        if (newSheet.getTables() != null) {
+            int tableCount = newSheet.getTables().getCount();
+            logger.info("[generateContinuedSheet] pageNo={}, 发现 table 数={}", pageNo, tableCount);
+            for (int t = 0; t < tableCount; t++) {
+                ITable table = newSheet.getTables().get(t);
+                IRange tableRange = table.getRange();
+                boolean inRange = tableRange.getRow() <= row + rowCount && row <= tableRange.getRow() + tableRange.getRowCount() - 1;
+                logger.info("[generateContinuedSheet] pageNo={}, table[{}] range=[{},{},{},{}] inRange={}",
+                        pageNo, t, tableRange.getRow(), tableRange.getColumn(),
+                        tableRange.getRowCount(), tableRange.getColumnCount(), inRange);
+                if (inRange) {
+                    table.setBindingPath(null);
+                    logger.info("[generateContinuedSheet] pageNo={}, 解绑 table: {}", pageNo, table.getName() != null ? table.getName() : "(unnamed)");
+                }
+            }
+        } else {
+            logger.info("[generateContinuedSheet] pageNo={}, 无 table 对象", pageNo);
+        }
+
+        int rowsPerPage = rowCount + 1;
+        int bindingPathModCount = 0;
         for (int i = 0; i <= colCount; i++) {
             for (int j = 0; j <= rowCount; j++) {
-                String bindingPath = sourceSheet.getRange(row + j, col + i).getBindingPath();
+                String bindingPath = newSheet.getRange(row + j, col + i).getBindingPath();
                 if (bindingPath != null && bindingPath.contains("_")) {
                     String prefix = bindingPath.substring(0, bindingPath.lastIndexOf("_") + 1);
                     String numStr = bindingPath.substring(bindingPath.lastIndexOf("_") + 1);
                     try {
                         int num = Integer.parseInt(numStr);
-                        int newNum = num + (rowCount + 1) * (sheetNum - 1);
+                        int newNum = num + rowsPerPage * (pageNo - 1);
                         String newPath = prefix + newNum;
                         newSheet.getRange(row + j, col + i).setBindingPath(newPath);
                         if (!existingColCodes.contains(newPath) && !newColCodes.contains(newPath)) {
                             newColCodes.add(newPath);
                         }
+                        if (bindingPathModCount < 5 || (i == 0 && j == 0)) {
+                            logger.info("[generateContinuedSheet] pageNo={}, 修改绑定路径: {} -> {}", pageNo, bindingPath, newPath);
+                        }
+                        bindingPathModCount++;
                     } catch (NumberFormatException ignored) {
+                        logger.warn("[generateContinuedSheet] pageNo={}, 解析数字失败: bindingPath={}", pageNo, bindingPath);
                     }
                 }
             }
         }
+        logger.info("[generateContinuedSheet] pageNo={}, 共修改绑定路径 {} 个", pageNo, bindingPathModCount);
+        logger.info("[generateContinuedSheet] pageNo={}, 完成, afterSheetCount={}", pageNo, workbook.getWorksheets().getCount());
     }
 
 
@@ -736,6 +909,26 @@ public class GrapeCityController {
         }
     }
 
+    private void fillCellsByBindingPath(IWorksheet worksheet, Map<String, Object> data) {
+        if (worksheet == null || data == null || data.isEmpty()) return;
+
+        int filled = 0;
+        for (int r = 0; r < worksheet.getRowCount(); r++) {
+            for (int c = 0; c < worksheet.getColumnCount(); c++) {
+                IRange range = worksheet.getRange(r, c);
+                String bindingPath = range.getBindingPath();
+                if (bindingPath != null && data.containsKey(bindingPath)) {
+                    Object value = data.get(bindingPath);
+                    if (!(value instanceof Collection<?>) && !(value instanceof JSONArray)) {
+                        range.setValue(value);
+                        filled++;
+                    }
+                }
+            }
+        }
+        logger.info("[fillCellsByBindingPath] sheet={}, filled={}", worksheet.getName(), filled);
+    }
+
     /**
      * 在 setDataSource 之前收集所有表格第一行数据行的合并单元格规则
      */

+ 1 - 1
src/main/resources/logback-spring.xml

@@ -45,7 +45,7 @@
     </appender>
 
     <!-- 项目日志级别 -->
-    <logger name="com.grapecity" level="DEBUG"/>
+    <logger name="com.grapecity.controller" level="DEBUG"/>
 
     <!-- 根日志:INFO 级别 -->
     <root level="INFO">