Selaa lähdekoodia

批量插入编号

cr 2 tuntia sitten
vanhempi
commit
82b0b45fd1

+ 50 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/BatchAddNumbersDTO.java

@@ -0,0 +1,50 @@
+package org.springblade.manager.dto;
+
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class BatchAddNumbersDTO {
+    @ApiModelProperty(value = "表单的pkeyId")
+    private Long pkeyId; //表单的pkeyId
+    @ApiModelProperty(value = "节点id")
+    private String nodeId; //节点id
+    @ApiModelProperty(value = "1施工 2监理")
+    private String classify; //1施工 2监理
+    @ApiModelProperty(value = "表单选中的key")
+    private String key; //表单选中的key
+    @ApiModelProperty(value = "1独立编号  2组合编号")
+    private Integer type;//  1独立编号  2组合编号
+
+    @ApiModelProperty(value = "1常规递增 2奇数递增 3偶数递增")
+    private Integer increType1; // 1常规递增 2奇数递增 3偶数递增
+    @ApiModelProperty(value = "1固定循环  2递增循环")
+    private Integer cycleType1; // 1固定循环  2递增循环
+    @ApiModelProperty(value = "循环组数")
+    private Integer cycleTypeGroup1; // 循环组数
+    @ApiModelProperty(value = "开始编号")
+    private Integer startNumber1; //开始编号
+    @ApiModelProperty(value = "结束编号")
+    private Integer endNumber1; //结束编号
+
+    /**
+     * 组合编号的情况才会有以下参数
+     */
+    @ApiModelProperty(value = "分隔符  - . / _ : 、")
+    private String separator; //分隔符  - . / _ : 、
+    @ApiModelProperty(value = "1常规递增 2奇数递增 3偶数递增")
+    private Integer increType2; // 1常规递增 2奇数递增 3偶数递增
+    @ApiModelProperty(value = "1固定循环  2递增循环")
+    private Integer cycleType2; // 1固定循环  2递增循环
+    @ApiModelProperty(value = "循环组数")
+    private Integer cycleTypeGroup2; // 循环组数
+    @ApiModelProperty(value = "开始编号")
+    private Integer startNumber2; //开始编号
+    @ApiModelProperty(value = "结束编号")
+    private Integer endNumber2; //结束编号
+}

+ 12 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java

@@ -54,6 +54,7 @@ import org.springblade.core.tool.constant.BladeConstant;
 import org.springblade.core.tool.utils.*;
 import org.springblade.manager.bean.TableInfo;
 import org.springblade.manager.dto.AddBussFileSortDTO;
+import org.springblade.manager.dto.BatchAddNumbersDTO;
 import org.springblade.manager.entity.*;
 import org.springblade.manager.enums.ExecuteType;
 import org.springblade.manager.mapper.ExcelTabMapper;
@@ -4191,6 +4192,17 @@ public class ExcelTabController extends BladeController {
     }
 
 
+    /**
+     * 批量插入编号
+     * @return
+     */
+    @PostMapping("/batchAddNumbers")
+    @ApiOperation(value = "批量插入编号", notes = "批量插入编号")
+    public R batchAddNumbers(@RequestBody  BatchAddNumbersDTO dto) throws Exception {
+        return excelTabService.batchAddNumbers(dto);
+    }
+
+
 
     @GetMapping("/checkParamElement")
     @ApiOperationSupport(order = 30)

+ 3 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/IExcelTabService.java

@@ -24,6 +24,7 @@ import org.springblade.business.dto.TrialSelfInspectionRecordDTO;
 import org.springblade.core.mp.base.BaseService;
 import org.springblade.core.tool.api.R;
 import org.springblade.manager.bean.TableInfo;
+import org.springblade.manager.dto.BatchAddNumbersDTO;
 import org.springblade.manager.entity.ExcelEditCallback;
 import org.springblade.manager.entity.ExcelTab;
 import org.springblade.manager.entity.WbsFormElement;
@@ -219,4 +220,6 @@ public interface IExcelTabService extends BaseService<ExcelTab> {
     void cancelSample(Long groupId, Long pkeyId, Long contractId, Long sampleId1) throws Exception;
 
     void expailHtmlInfo(String thmlUrl, Long id, String s) throws Exception ;
+
+    R batchAddNumbers(BatchAddNumbersDTO dto) throws Exception;
 }

+ 444 - 5
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ExcelTabServiceImpl.java

@@ -18,6 +18,7 @@ import com.spire.xls.ExcelPicture;
 import com.spire.xls.FileFormat;
 import com.spire.xls.Worksheet;
 import com.spire.xls.collections.PicturesCollection;
+import io.swagger.models.auth.In;
 import lombok.AllArgsConstructor;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang.StringUtils;
@@ -59,6 +60,7 @@ import org.springblade.core.tool.node.ForestNodeMerger;
 import org.springblade.core.tool.utils.DateUtil;
 import org.springblade.core.tool.utils.*;
 import org.springblade.manager.bean.TableInfo;
+import org.springblade.manager.dto.BatchAddNumbersDTO;
 import org.springblade.manager.dto.FormData;
 import org.springblade.manager.entity.*;
 import org.springblade.manager.enums.ExecuteType;
@@ -88,6 +90,7 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.support.DefaultTransactionDefinition;
 
 import java.io.*;
+import java.net.HttpURLConnection;
 import java.net.URL;
 import java.nio.file.Files;
 import java.text.ParseException;
@@ -867,7 +870,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
                 String file_path = FileUtils.getSysLocalFileUrl();
                 String filecode = SnowFlakeUtil.getId() + "";
                 String dataUrl = file_path + "/excel/" + filecode + ".xlsx";
-                java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();
+                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                 connection.setRequestMethod("GET");
                 connection.setConnectTimeout(5 * 1000);
                 InputStream inputStream = connection.getInputStream();
@@ -3405,7 +3408,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
         return list;
     }
 
-    private boolean  tableContextReplace(org.apache.poi.ss.usermodel.Workbook workbook, String oldContext, String newContext){
+    private boolean  tableContextReplace(Workbook workbook, String oldContext, String newContext){
         Sheet sheet = workbook.getSheetAt(0);
         sheet.setForceFormulaRecalculation(true);
         int rowNum = sheet.getLastRowNum();
@@ -3497,7 +3500,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
         }
 
         //获取清表excel文件
-        org.apache.poi.ss.usermodel.Workbook workbook = WorkbookFactory.create(Objects.requireNonNull(CommonUtil.getOSSInputStreamTow(excelTab.getFileUrl())));
+        Workbook workbook = WorkbookFactory.create(Objects.requireNonNull(CommonUtil.getOSSInputStreamTow(excelTab.getFileUrl())));
         Sheet sheet = workbook.getSheetAt(0);
         sheet.setForceFormulaRecalculation(true);
         Header header = sheet.getHeader();
@@ -4534,7 +4537,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
                             newStyle.cloneStyleFrom(cell.getCellStyle());
                             newStyle.setFont(redFont);
                             newStyle.setShrinkToFit(true);
-                            cell.setCellStyle(newStyle);
+                            //cell.setCellStyle(newStyle);
                             if(dqid.contains("||")){
                                 String[] split = dqid.split("\\|\\|");
                                 for (String singleDqid : split) {
@@ -4544,6 +4547,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
                                 sign.add(dqid);
                             }
                             cell.setCellValue(dqid);
+                            System.out.println(111);
                         }
                     }
                 }
@@ -4577,8 +4581,142 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
         FileUtils.excelToPdf(excelPath, pdfPath);
         PdfAddimgUtil.pdfAddImgInfo(pdfPath, String.join(",", sign),textMap);
         return R.data(FileUtils.getNetUrl(pdfPath));
+
     }
 
+//    @Override
+//    public R getPriWbsPdfByPId(Long pkeyId) throws Exception {
+//        // 1. 获取文件存储路径
+//        String file_path = FileUtils.getSysLocalFileUrl();
+//
+//        // 2. 查询 WbsTreePrivate 数据
+//        WbsTreePrivate wbsTreePrivate = this.wbsTreePrivateService.getOne(
+//                Wrappers.<WbsTreePrivate>lambdaQuery()
+//                        .eq(WbsTreePrivate::getPKeyId, pkeyId)
+//        );
+//        if (wbsTreePrivate == null) {
+//            return R.fail("未获取到该表单的信息");
+//        }
+//        if (wbsTreePrivate.getHtmlUrl() == null) {
+//            return R.fail("htmlUrl is null");
+//        }
+//
+//        // 3. 定义 PDF 和 Excel 存储路径
+//        String pdfPath = file_path + "/pdf/" + pkeyId + ".pdf";
+//        String excelPath = file_path + "/pdf/" + pkeyId + ".xlsx";
+//
+//        // 4. 删除已存在的旧文件(避免缓存问题)
+//        File tabPdf = ResourceUtil.getFile(pdfPath);
+//        if (tabPdf.exists()) {
+//            tabPdf.delete();
+//        }
+//
+//        // 5. 获取清表信息(Excel 模板)
+//        ExcelTab excelTab = this.getById(wbsTreePrivate.getExcelId());
+//        if (excelTab == null) {
+//            return R.fail("未获取到清表信息");
+//        }
+//
+//        // 6. 从 OSS 获取 Excel 模板输入流
+//        InputStream excelInp = CommonUtil.getOSSInputStream(excelTab.getFileUrl());
+//        Workbook workbook;
+//        String suffix = excelTab.getFileUrl().substring(excelTab.getFileUrl().lastIndexOf("."));
+//
+//        // 7. 根据文件后缀创建 Workbook(支持 .xls 和 .xlsx)
+//        if (".xls".equalsIgnoreCase(suffix)) {
+//            workbook = new HSSFWorkbook(excelInp);
+//        } else if (".xlsx".equalsIgnoreCase(suffix)) {
+//            workbook = new XSSFWorkbook(excelInp);
+//        } else {
+//            return R.fail("不支持的 Excel 格式");
+//        }
+//
+//        // 8. 获取第一个 Sheet 并强制重新计算公式
+//        Sheet sheet = workbook.getSheetAt(0);
+//        sheet.setForceFormulaRecalculation(true);
+//
+//        // 9. 解析 HTML 并提取 dqid
+//        List<String> sign = new ArrayList<>();
+//        if (StringUtils.isNotEmpty(wbsTreePrivate.getHtmlUrl())) {
+//            InputStream htmlInputStream = FileUtils.getInputStreamByUrl(wbsTreePrivate.getHtmlUrl());
+//            String htmlString = IoUtil.readToString(htmlInputStream);
+//
+//            // 10. 使用 Jsoup 解析 HTML
+//            Document doc = Jsoup.parse(htmlString);
+//
+//            // 11. 查找所有含 dqid 的元素(不再限定在 table 内)
+//            Elements dqidElements = doc.getElementsByAttribute("dqid");
+//            System.out.println("找到 " + dqidElements.size() + " 个含 dqid 的元素");
+//
+//            for (Element element : dqidElements) {
+//                String dqid = element.attr("dqid");
+//                System.out.println("处理 dqid: " + dqid);
+//
+//                // 12. 检查是否有 x1 和 y1 属性(用于定位 Excel 单元格)
+//                if (element.hasAttr("x1") && element.hasAttr("y1")) {
+//                    int x1 = Func.toInt(element.attr("x1"));
+//                    int y1 = Func.toInt(element.attr("y1"));
+//                    System.out.println("坐标: x1=" + x1 + ", y1=" + y1);
+//
+//                    // 13. 获取 Excel 单元格(行和列索引从 0 开始)
+//                    Row row = sheet.getRow(y1 - 1);
+//                    if (row == null) {
+//                        System.out.println("警告: 第 " + (y1 - 1) + " 行不存在,自动创建");
+//                        row = sheet.createRow(y1 - 1);
+//                    }
+//
+//                    Cell cell = row.getCell(x1 - 1, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
+//                    System.out.println("单元格原始值: " + cell.getStringCellValue());
+//
+//                    // 14. 强制设置单元格为文本格式(避免科学计数法问题)
+//                    CellStyle textStyle = workbook.createCellStyle();
+//                    textStyle.setDataFormat(workbook.createDataFormat().getFormat("@"));
+//                    cell.setCellStyle(textStyle);
+//
+//                    // 15. 设置 dqid 值
+//                    cell.setCellValue(dqid);
+//                    System.out.println("成功写入 dqid: " + dqid);
+//
+//                    // 16. 处理多个 dqid(用 || 分隔的情况)
+//                    if (dqid.contains("||")) {
+//                        String[] split = dqid.split("\\|\\|");
+//                        Collections.addAll(sign, split);
+//                    } else {
+//                        sign.add(dqid);
+//                    }
+//                } else {
+//                    System.out.println("警告: 元素缺少 x1 或 y1 属性,跳过");
+//                }
+//            }
+//        }
+//
+//        // 17. 查询电签配置信息
+//        TextdictInfoVO textdictInfo = new TextdictInfoVO();
+//        textdictInfo.setType(2);
+//        textdictInfo.setExcelId(wbsTreePrivate.getExcelId() + "");
+//        textdictInfo.setTabId(pkeyId + "");
+//        textdictInfo.setShowType(1);
+//
+//        Query query = new Query();
+//        query.setCurrent(0);
+//        query.setSize(100);
+//        IPage<TextdictInfoVO> pages = textdictInfoService.selectTextdictInfoPage(Condition.getPage(query), textdictInfo);
+//        Map<Long, TextdictInfo> textMap = pages.getRecords().stream()
+//                .collect(Collectors.toMap(TextdictInfo::getId, Function.identity()));
+//
+//        // 18. 写入 Excel 文件
+//        try (FileOutputStream outputStream = new FileOutputStream(excelPath)) {
+//            workbook.write(outputStream);
+//        }
+//
+//        // 19. 转换为 PDF 并添加电子签名
+//        FileUtils.excelToPdf(excelPath, pdfPath);
+//        PdfAddimgUtil.pdfAddImgInfo(pdfPath, String.join(",", sign), textMap);
+//
+//        // 20. 返回 PDF 网络路径
+//        return R.data(FileUtils.getNetUrl(pdfPath));
+//    }
+
 
     /**
      * 试验 委托单 单pdf
@@ -4619,7 +4757,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
         }
 
         //获取清表excel文件
-        org.apache.poi.ss.usermodel.Workbook workbook = WorkbookFactory.create(Objects.requireNonNull(CommonUtil.getOSSInputStreamTow(excelTab.getFileUrl())));
+        Workbook workbook = WorkbookFactory.create(Objects.requireNonNull(CommonUtil.getOSSInputStreamTow(excelTab.getFileUrl())));
         Sheet sheet = workbook.getSheetAt(0);
         sheet.setForceFormulaRecalculation(true);
 
@@ -6128,6 +6266,307 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
         FileUtil.writeToFile(writefile, doc.html(), Boolean.parseBoolean("UTF-8"));
     }
 
+
+    @Override
+    public R batchAddNumbers(BatchAddNumbersDTO dto) throws Exception {
+       //先查出当前节点下所有的表单
+        String selectAllNodeTable;
+        if(dto.getClassify().equals("1")){
+            selectAllNodeTable="select * from m_wbs_tree_contract where p_id="+dto.getNodeId()+" and table_owner in (1,2,3) and is_deleted=0";
+        }else {
+            selectAllNodeTable="select * from m_wbs_tree_contract where p_id="+dto.getNodeId()+" and table_owner in (4,5,6) and is_deleted=0";
+        }
+        List<WbsTreeContract> tables = jdbcTemplate.query(selectAllNodeTable, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+        String selectTable="select * from m_wbs_tree_contract where p_key_id="+dto.getPkeyId();
+        WbsTreeContract wbsContract = jdbcTemplate.queryForObject(selectTable, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+        Long excelId = wbsContract.getExcelId();
+        String nodeName = wbsContract.getNodeName();
+        int sufix =0;
+        if(nodeName.contains("_")){
+            sufix = Integer.parseInt(nodeName.substring(nodeName.lastIndexOf("_") +1));
+        }
+        List<WbsTreeContract>resultList=new ArrayList<>();
+        for (WbsTreeContract table : tables) {
+            if(Objects.equals(table.getExcelId(), excelId)){
+                if(sufix!=0&&table.getNodeName().contains("_")){
+                    int suffix = Integer.parseInt(table.getNodeName().substring(nodeName.lastIndexOf("_") +1));
+                    if(suffix>=sufix){
+                        resultList.add(table);
+                    }
+                }else if(sufix==0){
+                    resultList.add(table);
+                }
+            }
+        }
+        resultList.sort(new ExcelTabServiceImpl.WbsTreeContractComparator());
+        List<String> numbers = generateNumbers(dto);
+        Map<WbsTreeContract,  Map<String,String>> reData = new HashMap<>();
+        String key = dto.getKey();
+        String[] strings = key.split("__");
+        int i=0;
+        boolean flag=false;
+        for (WbsTreeContract contract : resultList) {
+            if(flag){
+                break;
+            }
+            Map<String,String> map=new HashMap<>();
+            String fileUrl = contract.getHtmlUrl();
+            InputStream fileInputStream = FileUtils.getInputStreamByUrl(fileUrl);
+            String htmlString = IoUtil.readToString(fileInputStream);
+            Document doc = Jsoup.parse(htmlString);
+            Elements keyNames = doc.getElementsByAttribute("keyname");
+            for (Element keyName : keyNames) {
+                String result = keyName.attr("keyname");
+                if(result.contains(strings[0])){
+                    if (i < numbers.size()) {
+                        map.put(result, numbers.get(i));
+                        i++;
+                    } else {
+                        flag=true;
+                        break;
+                    }
+                }
+            }
+            if(!map.isEmpty()){
+                reData.put(contract,map);
+            }
+        }
+        //保存数据入库并且生成pdf
+        saveDataAndGeneratePdf(reData,dto.getNodeId(),dto.getClassify(),wbsContract.getContractId(),wbsContract.getProjectId());
+        return null;
+    }
+
+    private void saveDataAndGeneratePdf(Map<WbsTreeContract, Map<String, String>> reData,String nodeId,String classify,String contractId,String projectId) throws Exception {
+        for (Map.Entry<WbsTreeContract, Map<String, String>> entry : reData.entrySet()) {
+            Map<String, String> dataInfo2 = entry.getValue();
+            WbsTreeContract wbsTreeContract = entry.getKey();
+            //计算数据
+            LinkedHashMap<String, List<String>> dataMap = dataInfo2.keySet().stream().filter(e -> e.contains("__")).collect(Collectors.groupingBy(e -> e.split("__")[0], LinkedHashMap<String, List<String>>::new, Collectors.toList()));
+            LinkedHashMap<String, String> dataMap2 = new LinkedHashMap<>();
+            //字段组合
+            for (String k : dataMap.keySet()) {
+                if (dataMap.get(k).size() > 1 && !dataMap.get(k).contains("000Z")) {
+                    String[] ziduan = dataMap.get(k).toArray(new String[]{});
+                    String temp = "";
+                    for (int i = 0; i < ziduan.length - 1; i++) {
+                        for (int j = 0; j < ziduan.length - i - 1; j++) {
+                            int tr = Integer.parseInt((ziduan[j].split("__")[1]).split("_")[0]);
+                            int td = Integer.parseInt(ziduan[j].split("__")[1].split("_")[1]);
+                            int tr_1 = Integer.parseInt(ziduan[j + 1].split("__")[1].split("_")[0]);
+                            int td_1 = Integer.parseInt(ziduan[j + 1].split("__")[1].split("_")[1]);
+                            if (tr > tr_1 && td == td_1) { //纵向排序
+                                temp = ziduan[j];
+                                ziduan[j] = ziduan[j + 1];
+                                ziduan[j + 1] = temp;
+                            }
+                        }
+                    }
+                    StringBuilder lastStr = new StringBuilder(dataInfo2.get(ziduan[0]) + "_^_" + ziduan[0].split("__")[1]);
+                    for (int i = 1; i < ziduan.length; i++) {
+                        String keyData = dataInfo2.get(ziduan[i]);
+                        if (keyData!=null && Func.isNotEmpty(keyData)) {
+                            lastStr.append("☆").append(dataInfo2.get(ziduan[i])).append("_^_").append(ziduan[i].split("__")[1]);
+                        }
+
+                    }
+                    dataMap2.put(k, lastStr.toString());
+                } else {
+                    String dataVal = dataInfo2.get(dataMap.get(k).get(0));
+                    if(StringUtils.isNotEmpty(dataVal)){
+                        dataMap2.put(k, dataVal + "_^_" + dataMap.get(k).get(0).split("__")[1]);
+                    }
+                }
+            }
+            Optional<String> Key = dataMap2.keySet().stream().findFirst();
+            if(Key.isPresent()){
+                String sql="update "+wbsTreeContract.getInitTableName()+" set "+Key.get()+" = '"+dataMap2.get(Key.get())+"' where p_key_id = "+wbsTreeContract.getPKeyId();
+                jdbcTemplate.update(sql);
+            }
+            getBussPdfInfo(wbsTreeContract.getPKeyId());
+        }
+        getBussPdfs(nodeId, classify, contractId, projectId);
+    }
+
+    /**
+     * 生成符合要求的编号列表
+     * @param dto 包含编号生成规则的参数对象
+     * @return 生成的编号列表
+     */
+    public static List<String> generateNumbers(BatchAddNumbersDTO dto) {
+        List<String> result = new ArrayList<>();
+
+        if (dto.getType() == 1) {
+            // 处理独立编号
+            result = generateSinglePartNumbers(
+                    dto.getIncreType1(),
+                    dto.getCycleType1(),
+                    dto.getCycleTypeGroup1(),
+                    dto.getStartNumber1(),
+                    dto.getEndNumber1()
+            );
+        } else if (dto.getType() == 2) {
+            // 处理组合编号
+            List<String> part1List = generateSinglePartNumbers(
+                    dto.getIncreType1(),
+                    dto.getCycleType1(),
+                    dto.getCycleTypeGroup1(),
+                    dto.getStartNumber1(),
+                    dto.getEndNumber1()
+            );
+
+            List<String> part2List = generateSinglePartNumbers(
+                    dto.getIncreType2(),
+                    dto.getCycleType2(),
+                    dto.getCycleTypeGroup2(),
+                    dto.getStartNumber2(),
+                    dto.getEndNumber2()
+            );
+
+            // 组合两部分编号
+            for (String part1 : part1List) {
+                for (String part2 : part2List) {
+                    result.add(part1 + dto.getSeparator() + part2);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 生成单部分编号(独立编号或组合编号的一部分)
+     * @param increType 递增类型:1常规递增 2奇数递增 3偶数递增
+     * @param cycleType 循环类型:1固定循环 2递增循环
+     * @param cycleGroup 循环组数
+     * @param startNum 开始编号
+     * @param endNum 结束编号
+     * @return 生成的单部分编号列表
+     */
+    private static List<String> generateSinglePartNumbers(
+            Integer increType,
+            Integer cycleType,
+            Integer cycleGroup,
+            Integer startNum,
+            Integer endNum
+    ) {
+        List<String> result = new ArrayList<>();
+        // 获取符合递增条件的基础编号列表
+        List<String> baseNumbers = getBaseNumbers(increType, startNum, endNum);
+
+        if (cycleType == 1) {
+            // 固定循环:每个编号重复n次
+            for (String num : baseNumbers) {
+                for (int i = 0; i < cycleGroup; i++) {
+                    result.add(num);
+                }
+            }
+        } else if (cycleType == 2) {
+            // 递增循环:整个序列重复n次
+            for (int i = 0; i < cycleGroup; i++) {
+                result.addAll(baseNumbers);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 获取符合递增条件的基础编号列表
+     * @param increType 递增类型
+     * @param startNum 开始编号
+     * @param endNum 结束编号
+     * @return 符合条件的基础编号列表
+     */
+    private static List<String> getBaseNumbers(Integer increType, Integer startNum, Integer endNum) {
+        List<String> baseNumbers = new ArrayList<>();
+
+        for (int i = startNum; i <= endNum; i++) {
+            boolean isQualified = false;
+
+            if (increType == 1) {
+                // 常规递增:所有数字都符合条件
+                isQualified = true;
+            } else if (increType == 2) {
+                // 奇数递增:只保留奇数
+                isQualified = (i % 2 != 0);
+            } else if (increType == 3) {
+                // 偶数递增:只保留偶数
+                isQualified = (i % 2 == 0);
+            }
+
+            if (isQualified) {
+                baseNumbers.add(String.valueOf(i));
+            }
+        }
+
+        return baseNumbers;
+    }
+
+    public static class WbsTreeContractComparator implements Comparator<WbsTreeContract> {
+
+        @Override
+        public int compare(WbsTreeContract o1, WbsTreeContract o2) {
+            // 先比较 sort 属性
+            Integer sort1 = o1.getSort();
+            Integer sort2 = o2.getSort();
+            if (sort1 == null && sort2 == null) {
+                // 两个 sort 都为空,认为相等
+                return 0;
+            } else if (sort1 != null && sort2 != null && sort1 == 0 && sort2 == 0) {
+                // 两个 sort 都不为空,并且都是0
+                return 0;
+            } else if (sort1 == null) {
+                // 第一个 sort 为空,排在后面
+                return 1;
+            } else if (sort2 == null) {
+                // 第二个 sort 为空,排在前面
+                return -1;
+            } else {
+                // 两个 sort 都不为空,按 sort 排序
+                int sortComparison = Integer.compare(sort1, sort2);
+                if (sortComparison != 0) {
+                    return sortComparison;
+                }
+            }
+
+            // 如果 sort 相同,比较 nodeName
+            String name1 = o1.getNodeName();
+            String name2 = o2.getNodeName();
+            String[] split1 = name1.split("__");
+            String[] split2 = name2.split("__");
+            if (split1[0].equals(split2[0])) {
+                try {
+                    return compareTo(split1, split2);
+                } catch (NumberFormatException e) {
+                    // 如果无法将字符串转换为数字,则按照字符串比较
+                    return name1.compareTo(name2);
+                }
+            } else {
+                split1 = split1[0].split("_PL_");
+                split2 = split2[0].split("_PL_");
+                try {
+                    return compareTo(split1, split2);
+                } catch (NumberFormatException e) {
+                    return name1.compareTo(name2);
+                }
+            }
+        }
+
+        private static int compareTo(String[] split1, String[] split2) {
+            if (split1.length > 1 && split2.length > 1) {
+                int number1 = Integer.parseInt(split1[1]);
+                int number2 = Integer.parseInt(split2[1]);
+                return Integer.compare(number1, number2);
+            } else if (split1.length == 1 && split2.length == 1) {
+                return 0;
+            } else if (split1.length > 1) {
+                return 1;
+            } else {
+                return -1;
+            }
+        }
+    }
+
     // 获取解析样式
     public static Map<String, String> getHtmlStyle(Document doc) {
         Map<String, String> styleMap = new HashMap<>();