Kaynağa Gözat

Merge branch 'dev' of http://219.151.181.73:3000/zhuwei/bladex into cr

cr 1 ay önce
ebeveyn
işleme
68995a1a42
36 değiştirilmiş dosya ile 2206 ekleme ve 826 silme
  1. 7 2
      blade-common/src/main/java/org/springblade/common/utils/YiKeYunApiUtils.java
  2. 35 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/RecycleBinInfoDTO.java
  3. 135 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/entity/RecycleBinInfo.java
  4. 72 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/vo/RecycleBinInfoVO.java
  5. 6 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/TreeNodeVOByTabType.java
  6. 86 92
      blade-service/blade-business/src/main/java/org/springblade/business/controller/InformationWriteQueryController.java
  7. 11 4
      blade-service/blade-business/src/main/java/org/springblade/business/controller/MaterialProgressController.java
  8. 110 0
      blade-service/blade-business/src/main/java/org/springblade/business/controller/RecycleBinController.java
  9. 17 0
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/RecycleBinInfoMapper.java
  10. 95 0
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/RecycleBinInfoMapper.xml
  11. 25 0
      blade-service/blade-business/src/main/java/org/springblade/business/service/IRecycleBinInfoService.java
  12. 394 0
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/RecycleBinInfoServiceImpl.java
  13. 1 1
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/TrialSelfInspectionRecordServiceImpl.java
  14. 13 3
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/WbsTreeContractStatisticsServiceImpl.java
  15. 16 15
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/WeatherInfoServiceImpl.java
  16. 313 75
      blade-service/blade-business/src/main/java/org/springblade/business/utils/FileUtils.java
  17. 7 598
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java
  18. 15 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreePrivateController.java
  19. 1 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/feign/ExcelTabClientImpl.java
  20. 122 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/FormulaZhiZuo.java
  21. 1 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/SaveUserInfoByProjectMapper.xml
  22. 4 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/TextdictInfoMapper.java
  23. 4 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/TextdictInfoMapper.xml
  24. 2 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreePrivateMapper.java
  25. 56 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreePrivateMapper.xml
  26. 2 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IExcelTabService.java
  27. 5 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/ITextdictInfoService.java
  28. 2 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IWbsTreePrivateService.java
  29. 604 8
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ExcelTabServiceImpl.java
  30. 1 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaServiceImpl.java
  31. 6 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/TextdictInfoServiceImpl.java
  32. 3 7
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsFormElementServiceImpl.java
  33. 7 2
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeContractServiceImpl.java
  34. 8 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreePrivateServiceImpl.java
  35. 3 3
      blade-service/blade-manager/src/main/java/org/springblade/manager/utils/PdfAddimgUtil.java
  36. 17 13
      blade-service/blade-user/src/main/java/org/springblade/system/user/service/impl/UserServiceImpl.java

+ 7 - 2
blade-common/src/main/java/org/springblade/common/utils/YiKeYunApiUtils.java

@@ -100,8 +100,13 @@ public class YiKeYunApiUtils {
      * @param cityId
      * @return
      */
-    public static Map<String, Map<String, String>> getHistoryWeather(String cityId, Integer year, Integer month) {
-        String getUrl = String.format("http://gfeljm.tianqiapi.com/free/history?appid=%s&appsecret=%s&cityid=%s&year=%d&month=%d", API_YIKEYUN_APPID, API_YIKEYUN_APPSECRET, cityId, year, month);
+    public static Map<String, Map<String, String>> getHistoryWeather(String cityId, Integer year, Integer month, String date1) {
+        String getUrl;
+        if (date1 != null && date1.matches("\\d{4}-\\d{2}-\\d{2}")) {
+            getUrl = String.format("http://gfeljm.tianqiapi.com/free/history?appid=%s&appsecret=%s&cityid=%s&date=%s", API_YIKEYUN_APPID, API_YIKEYUN_APPSECRET, cityId, date1);
+        } else {
+            getUrl = String.format("http://gfeljm.tianqiapi.com/free/history?appid=%s&appsecret=%s&cityid=%s&year=%d&month=%d", API_YIKEYUN_APPID, API_YIKEYUN_APPSECRET, cityId, year, month);
+        }
         Map<String, Map<String, String>> map = new HashMap<>();
         try {
             URL url = new URL(getUrl);

+ 35 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/RecycleBinInfoDTO.java

@@ -0,0 +1,35 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.business.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+
+@Data
+public class RecycleBinInfoDTO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("ids")
+    private String ids;
+
+    @ApiModelProperty("是否恢复同批次全部删除数据, 0否,1是")
+    private Integer recoverOperationData;
+
+}

+ 135 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/entity/RecycleBinInfo.java

@@ -0,0 +1,135 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.business.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.core.mp.base.BaseEntity;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.utils.DateUtil;
+
+import java.util.Date;
+
+/**
+ * 实体类
+ *
+ * @author BladeX
+ * @since 2022-07-19
+ */
+@Data
+@TableName("u_recycle_bin_info")
+@EqualsAndHashCode(callSuper = true)
+public class RecycleBinInfo extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 项目ID
+     */
+    @ApiModelProperty("项目ID")
+    private Long projectId;
+    /**
+     * 合同段ID
+     */
+    @ApiModelProperty("合同段ID")
+    private Long contractId;
+
+    @ApiModelProperty("删除的id")
+    private Long delId;
+
+    @ApiModelProperty("删除位置id")
+    private Long delRootId;
+
+    @ApiModelProperty("删除位置名称")
+    private String delRootName;
+
+    @ApiModelProperty("操作记录id")
+    private String operationId;
+
+    /**
+     * 删除的类型,文件资料1,工程划分2,影像资料3
+     */
+    @ApiModelProperty("删除的类型,文件资料1,工程划分2,影像资料3")
+    private Integer delType;
+
+    /**
+     * 是否是资料节点,0否,1是
+     */
+    @ApiModelProperty("是否是资料节点,0否,1是")
+    private Integer isData;
+
+    /**
+     * 文件名称
+     */
+    @ApiModelProperty("文件名称(施工)")
+    private String fileName;
+
+    @ApiModelProperty("文件名称(监理)")
+    private String jlFileName;
+
+    /**
+     * 所在位置,即节点路径
+     */
+    @ApiModelProperty("所在位置,即节点路径")
+    private String position;
+    /**
+     * 业务ID
+     */
+    @ApiModelProperty("业务ID")
+    private String businessId;
+    /**
+     * 操作时间,与创建时间相同
+     */
+    @ApiModelProperty("操作时间,与创建时间相同")
+    private String operationTime;
+
+    @ApiModelProperty("操作人姓名")
+    private String createUserName;
+
+    @ApiModelProperty("更新者姓名")
+    private String updateUserName;
+
+    @TableField(exist = false)
+    @ApiModelProperty("删除信息")
+    private String delInfo;
+
+    public RecycleBinInfo(String businessIds, String title, Integer deletedType, String position, String projectId, String contractId) {
+        this.businessId = businessIds;
+        this.fileName = title;
+        this.delType = deletedType;
+        this.position = position;
+        this.projectId = Long.parseLong(projectId);
+        this.contractId = Long.parseLong(contractId);
+
+        this.setCreateUser(AuthUtil.getUserId());
+        this.createUserName = AuthUtil.getNickName();
+        Date now = new Date();
+        this.setCreateTime(now);
+        this.operationTime = DateUtil.format(now, "yyyy-MM-dd HH:mm:ss");
+    }
+
+    public RecycleBinInfo() {
+    }
+
+    public String getDelInfo() {
+        return this.getCreateUserName() == null ? "" : this.getCreateUserName() + "\n" + (this.getCreateTime() == null ? "" : DateUtil.format(this.getCreateTime(), "yyyy-MM-dd HH:mm:ss"));
+    }
+
+}

+ 72 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/vo/RecycleBinInfoVO.java

@@ -0,0 +1,72 @@
+package org.springblade.business.vo;
+
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.core.mp.support.Query;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.Date;
+
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class RecycleBinInfoVO extends Query implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 开始时间
+     */
+    @ApiModelProperty("开始时间")
+    private String startTime;
+    /**
+     * 结束时间
+     */
+    @ApiModelProperty("结束时间")
+    private String endTime;
+
+    /**
+     * 用户ID
+     */
+    @ApiModelProperty("用户ID")
+    private Long userId;
+
+    /**
+     * 合同段ID
+     */
+    @ApiModelProperty("合同段ID")
+    @NotNull
+    private Long contractId;
+
+    /**
+     * 是否是资料节点,0否,1是
+     */
+    @ApiModelProperty("是否是资料节点,0否,1是")
+    private Integer isData;
+
+    /**
+     * 0:删除台账, 1:恢复台账
+     */
+    @ApiModelProperty("0:删除台账, 1:恢复台账")
+    private Integer recycleType = 0;
+
+    /**
+     * 删除的类型,文件资料1,工程划分2,影像资料3
+     */
+    @ApiModelProperty("删除的类型,文件资料1,工程划分2,影像资料3")
+    private Integer delType = 2;
+
+    /**
+     * 内容
+     */
+    @ApiModelProperty("内容")
+    private String content;
+
+    @ApiModelProperty("操作记录id")
+    private String operationId;
+
+
+}

+ 6 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/TreeNodeVOByTabType.java

@@ -13,6 +13,7 @@ public class TreeNodeVOByTabType extends BaseNode<TreeNode> {
     private static final long serialVersionUID = 1L;
     private String title;
     private String primaryKeyId;
+    private Long pkeyId;
     private String tabType;
     private String tabOwner;
     private String elementTotal;
@@ -20,5 +21,10 @@ public class TreeNodeVOByTabType extends BaseNode<TreeNode> {
     private String initTableId;
     private String initTableName;
     private String excelIds;
+    private String excelId;
+
+    private String tableType;
+    private String tableOwner;
+    private String isLinkTable;
 
 }

+ 86 - 92
blade-service/blade-business/src/main/java/org/springblade/business/controller/InformationWriteQueryController.java

@@ -145,6 +145,7 @@ public class InformationWriteQueryController extends BladeController {
 
     private final IRecycleBinService recycleBinService;
     private final WbsTreeContractStatisticsClientImpl wbsTreeContractStatisticsClient;
+    private final IRecycleBinInfoService recycleBinInfoService;
 
 
     @Autowired
@@ -1949,11 +1950,14 @@ public R<Boolean> copyContractTreeNode(@RequestBody CopyContractTreeNodeVO vo) {
 
             //旧节点的pkeyId和新节点的pkeyId
             Map<Long, Long> oldPKeyIdToNewPKeyIdMap = new HashMap<>();
+            Map<Long, Long> oldIdToNewIdMap = new HashMap<>();
             //节点和表的id 与 节点/表 实体类
             Map<String, WbsTreeContract> nodeMap = new HashMap<>();
 
             nodeChildAll.forEach(node -> {
-                oldPKeyIdToNewPKeyIdMap.put(node.getPKeyId(),SnowFlakeUtil.getId());
+                Long id = SnowFlakeUtil.getId();
+                oldPKeyIdToNewPKeyIdMap.put(node.getPKeyId(), id);
+                oldIdToNewIdMap.put(node.getId(), id);
                 oldToNewIdMap.put(node.getId(), SnowFlakeUtil.getId());
                 nodeMap.put(node.getId().toString(), node);
             });
@@ -1969,53 +1973,55 @@ public R<Boolean> copyContractTreeNode(@RequestBody CopyContractTreeNodeVO vo) {
                         .distinct()
                         .collect(Collectors.toList());
                 //表名集合转逗号拼接的字符串
-                String inClausePlaceholders = String.join(",", Collections.nCopies(tabNames.size(), "?"));
-                String sql = "SELECT table_name AS queryType, GROUP_CONCAT(COLUMN_name) AS ancestors " +
-                        "FROM information_schema.COLUMNS WHERE table_name IN (" + inClausePlaceholders + ") " +
-                        "GROUP BY table_name";
-                Object[] params = tabNames.toArray();
-                //数据库中查询 表与表对应的列名
-                List<Map<String, Object>> results = jdbcTemplate.queryForList(sql, params);
-
-                //表名集合转Map key=表名 value=表对应的数据集合
-                Map<String, List<WbsTreeContract>> tabsGroup = nodeChildAll.stream()
-                        .filter(f -> f.getType() == 2 && ObjectUtil.isNotEmpty(f.getInitTableName()))
-                        .collect(Collectors.groupingBy(WbsTreeContract::getInitTableName));
-
-                Set<Long> continuePkeyIds = new HashSet<>();
-                for (Map<String, Object> result : results) {
-                    String tabName = (String) result.get("queryType");
-                    String col = (String) result.get("ancestors");
-                    if (ObjectUtil.isEmpty(col) || ObjectUtil.isEmpty(tabName)) {
-                        continue;
-                    }
-                    //字段集合 过滤掉不存在指定字段的表
-                    List<String> filteredList = Arrays.stream(col.split(","))
-                            .filter(value -> !value.equals("id") && !value.equals("p_key_id") && !value.equals("group_id"))
-                            .collect(Collectors.toList());
-                    //过滤之后的字段集合
-                    String keys = StringUtils.join(filteredList, ",");
-                    //根据表明查询指定数据集合
-                    List<WbsTreeContract> tabs = tabsGroup.get(tabName);
-                    for (WbsTreeContract tab : tabs) {
-                        // 在复制数据时,测量记录表 的数据不需要复制
-                        if(tab.getType()==2 && tab.getTableType()==6){
+                if (!tabNames.isEmpty()) {
+                    String inClausePlaceholders = String.join(",", Collections.nCopies(tabNames.size(), "?"));
+                    String sql = "SELECT table_name AS queryType, GROUP_CONCAT(COLUMN_name) AS ancestors " +
+                            "FROM information_schema.COLUMNS WHERE table_name IN (" + inClausePlaceholders + ") " +
+                            "GROUP BY table_name";
+                    Object[] params = tabNames.toArray();
+                    //数据库中查询 表与表对应的列名
+                    List<Map<String, Object>> results = jdbcTemplate.queryForList(sql, params);
+
+                    //表名集合转Map key=表名 value=表对应的数据集合
+                    Map<String, List<WbsTreeContract>> tabsGroup = nodeChildAll.stream()
+                            .filter(f -> f.getType() == 2 && ObjectUtil.isNotEmpty(f.getInitTableName()))
+                            .collect(Collectors.groupingBy(WbsTreeContract::getInitTableName));
+
+                    Set<Long> continuePkeyIds = new HashSet<>();
+                    for (Map<String, Object> result : results) {
+                        String tabName = (String) result.get("queryType");
+                        String col = (String) result.get("ancestors");
+                        if (ObjectUtil.isEmpty(col) || ObjectUtil.isEmpty(tabName)) {
                             continue;
                         }
-                        //根据字段
-                        String dataSql = "SELECT " + keys + " FROM " + tabName + " WHERE p_key_id = " + tab.getPKeyId() + " LIMIT 1;";
-                        try {
-                            //查询指定表指定表节点的数据
-                            Map<String, Object> resultMap = jdbcTemplate.queryForMap(dataSql);
-                            //删除空值
-                            resultMap.values().removeIf(value -> value == null || (value instanceof String && ObjectUtil.isEmpty(value)));
-                            colMaps.put(tab.getPKeyId(), resultMap);
-                        } catch (EmptyResultDataAccessException e) {
-                            continuePkeyIds.add(tab.getPKeyId());
+                        //字段集合 过滤掉不存在指定字段的表
+                        List<String> filteredList = Arrays.stream(col.split(","))
+                                .filter(value -> !value.equals("id") && !value.equals("p_key_id") && !value.equals("group_id"))
+                                .collect(Collectors.toList());
+                        //过滤之后的字段集合
+                        String keys = StringUtils.join(filteredList, ",");
+                        //根据表明查询指定数据集合
+                        List<WbsTreeContract> tabs = tabsGroup.get(tabName);
+                        for (WbsTreeContract tab : tabs) {
+                            // 在复制数据时,测量记录表 的数据不需要复制
+                            if(tab.getType()==2 && tab.getTableType()==6){
+                                continue;
+                            }
+                            //根据字段
+                            String dataSql = "SELECT " + keys + " FROM " + tabName + " WHERE p_key_id = " + tab.getPKeyId() + " LIMIT 1;";
+                            try {
+                                //查询指定表指定表节点的数据
+                                Map<String, Object> resultMap = jdbcTemplate.queryForMap(dataSql);
+                                //删除空值
+                                resultMap.values().removeIf(value -> value == null || (value instanceof String && ObjectUtil.isEmpty(value)));
+                                colMaps.put(tab.getPKeyId(), resultMap);
+                            } catch (EmptyResultDataAccessException e) {
+                                continuePkeyIds.add(tab.getPKeyId());
+                            }
                         }
                     }
+                    logger.info("以下元素表没有获取到对应实体表数据,已跳过 ===> 表pKeyId:[{}]", StringUtils.join(continuePkeyIds, ","));
                 }
-                logger.info("以下元素表没有获取到对应实体表数据,已跳过 ===> 表pKeyId:[{}]", StringUtils.join(continuePkeyIds, ","));
             }
             // 节点+表节点
             for (WbsTreeContract nodeOld : nodeChildAll) {
@@ -2068,7 +2074,7 @@ public R<Boolean> copyContractTreeNode(@RequestBody CopyContractTreeNodeVO vo) {
                     //如果不是  判断旧节点的父级id是否在旧节点集合中 如果在 则设置新节点的父级id为旧节点的父级id的映射id  否则就是随机id
                     newData.setParentId(oldToNewIdMap.containsKey(nodeOld.getParentId()) ? oldToNewIdMap.get(nodeOld.getParentId()) : SnowFlakeUtil.getId());
                     //TODO 20250414-lhb-新增
-                    newData.setPId(oldPKeyIdToNewPKeyIdMap.containsKey(nodeOld.getPId()) ? oldPKeyIdToNewPKeyIdMap.get(nodeOld.getPId()) : SnowFlakeUtil.getId());
+                    newData.setPId(oldPKeyIdToNewPKeyIdMap.containsKey(nodeOld.getPId()) ? oldPKeyIdToNewPKeyIdMap.get(nodeOld.getPId()) : oldToNewIdMap.get(nodeOld.getParentId()) ==  null ? SnowFlakeUtil.getId() : oldToNewIdMap.get(nodeOld.getParentId()));
                 }
                 newData.setCreateTime(new Date());
                 newData.setUpdateTime(new Date());
@@ -2148,17 +2154,7 @@ public R<Boolean> copyContractTreeNode(@RequestBody CopyContractTreeNodeVO vo) {
             //TODO 20250414-lhb-新增 添加祖级字段 ancestorsPId
             //因为复制选中节点,所以要查询出选中节点的父节点信息 来组装祖级节点
             if(needCopyNode != null){
-                Long parentPKeyId = null;
-                String ancestorsPId = null;
-                if(needCopyNode.getPId() == 0L){
-                    ancestorsPId = "0";
-                    parentPKeyId = 0L;
-                }else{
-                    WbsTreeContract parentNode = this.wbsTreeContractClient.getContractNodeByPrimaryKeyId(String.valueOf(needCopyNode.getPId()));
-                    ancestorsPId = parentNode.getAncestorsPId();
-                    parentPKeyId = parentNode.getPKeyId();
-                }
-                attachNodesToTarget(saveList,parentPKeyId,ancestorsPId);
+                attachNodesToTarget(saveList);
             }
         }
         needCopyNode.setNodeName(vo.getNeedCopyNodeName());
@@ -2335,17 +2331,7 @@ public R<Boolean> copyContractTreeNode(@RequestBody CopyContractTreeNodeVO vo) {
                             resultAll.addAll(addTabList);
                             //因为复制选中节点,所以要查询出选中节点的父节点信息 来组装祖级节点
                             if(needCopyNode != null && CollectionUtil.isNotEmpty(resultAll)){
-                                Long parentPKeyId = null;
-                                String ancestorsPId = null;
-                                if(needCopyNode.getPId() == 0L){
-                                    ancestorsPId = "0";
-                                    parentPKeyId = 0L;
-                                }else{
-                                    WbsTreeContract parentNode = this.wbsTreeContractClient.getContractNodeByPrimaryKeyId(String.valueOf(toCopyVO.getPrimaryKeyId()));
-                                    ancestorsPId = parentNode.getAncestorsPId();
-                                    parentPKeyId = parentNode.getPKeyId();
-                                }
-                                attachNodesToTarget(resultAll,parentPKeyId,ancestorsPId);
+                                attachNodesToTarget(resultAll);
                             }
                         }
                     }
@@ -2488,8 +2474,9 @@ private Map<String, String> reviseValue(WbsTreeContract wtc, WbsTreeContract par
     if (wtc != null) {
         try {
             String tableName = wtc.getInitTableName();
-            if (!ekvMap.containsKey(tableName)) {
-                Map<String, String> map = ekvMap.computeIfAbsent(wtc.getInitTableName(), K -> new HashMap<>());
+//            if (!ekvMap.containsKey(tableName)) {
+//                Map<String, String> map = ekvMap.computeIfAbsent(wtc.getInitTableName(), K -> new HashMap<>());
+                Map<String, String> map = new HashMap<>();
                 if (parent == null) {
                     parent = this.wbsTreeContractClient.getContractWbsTreeByContractIdAndId(wtc.getParentId(), Long.parseLong(wtc.getContractId()));
                 }
@@ -2525,8 +2512,9 @@ private Map<String, String> reviseValue(WbsTreeContract wtc, WbsTreeContract par
                         });
                     }
                 }
-            }
-            return ekvMap.getOrDefault(tableName, new HashMap<>());
+//            }
+//            return ekvMap.getOrDefault(tableName, new HashMap<>());
+            return map;
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -2581,6 +2569,10 @@ private Object reviseValue(Map<String, String> p2, String key, Object value) {
                     /*重做随机值*/
                     List<RangeJson> rjs = JSON.parseArray(setting, RangeJson.class);
                     if (value != null && !value.toString().isEmpty() && Func.isNotEmpty(rjs)) {
+//                        List<RangeJson> temp = rjs.stream().filter(rj -> rj.getPkeyId().equals(pKeyId)).collect(Collectors.toList());
+//                        if (temp.isEmpty()) {
+//                            temp = rjs;
+//                        }
                         List<RangeJson> rangeJsons = rjs.stream().filter(rj -> rj.getPkeyId().equals(pKeyId)).collect(Collectors.toList());
                         if (!rangeJsons.isEmpty()) {
                             List<String[]> la = Arrays.stream(value.toString().split("☆")).map(s -> s.split("_\\^_")).collect(Collectors.toList());
@@ -3650,23 +3642,21 @@ public R removeContractTreeNodeJudge(@RequestParam String ids) {
                 String wbsIds = removeList.stream().map(Object::toString).collect(Collectors.joining(","));
                 /** 判断是否子节点有上报或审批过的资料,因为父id和祖级节点错误,直接使用上面的值去查询上报情况*/
                 List<String> unRemoveIds = jdbcTemplate.queryForList("SELECT wbs_id from u_information_query WHERE wbs_id in (" + wbsIds + ") and is_deleted = 0 and status in (1,2) GROUP BY wbs_id HAVING count(1) > 0", String.class);
-                if (!unRemoveIds.isEmpty()) {
-                    // 剔除此节点
-                    Map<String, String> map = unRemoveIds.stream().collect(Collectors.toMap(id -> id, Function.identity()));
-                    removeNodeList.removeIf(node -> {
-                        //删除掉表格 TODO(不清楚为什么要剔除表格,按理说删除节点后,节点下的表也应该一起删除的,猜测或许是为了方便恢复节点的时候表数据还存在)2023年9月19日
-                        boolean removeIf = node.getType() != null && new Integer("2").equals(node.getType());
-                        boolean equals = map.containsKey(node.getPKeyId().toString());
-                        if (equals) {
-                            unremoveNodeMap.put(node.getPKeyId(), node);
-                        }
-                        return removeIf || equals;
-                    });
-                    Map<Long, WbsTreeContract> contractMap = removeNodeList.stream().collect(Collectors.toMap(WbsTreeContract::getId, Function.identity()));
-                    Map<Long, WbsTreeContract> tempMap = new HashMap<>();
-                    unremoveNodeMap.forEach((key, value) -> collectNodeAndAncestors(value, contractMap, tempMap));
-                    unremoveNodeMap.putAll(tempMap);
-                }
+                // 剔除此节点
+                Map<String, String> map = unRemoveIds.stream().collect(Collectors.toMap(id -> id, Function.identity()));
+                removeNodeList.removeIf(node -> {
+                    //删除掉表格 TODO(不清楚为什么要剔除表格,按理说删除节点后,节点下的表也应该一起删除的,猜测或许是为了方便恢复节点的时候表数据还存在)2023年9月19日
+                    boolean removeIf = node.getType() != null && new Integer("2").equals(node.getType());
+                    boolean equals = map.containsKey(node.getPKeyId().toString());
+                    if (equals) {
+                        unremoveNodeMap.put(node.getPKeyId(), node);
+                    }
+                    return removeIf || equals;
+                });
+                Map<Long, WbsTreeContract> contractMap = removeNodeList.stream().collect(Collectors.toMap(WbsTreeContract::getId, Function.identity()));
+                Map<Long, WbsTreeContract> tempMap = new HashMap<>();
+                unremoveNodeMap.forEach((key, value) -> collectNodeAndAncestors(value, contractMap, tempMap));
+                unremoveNodeMap.putAll(tempMap);
             }
             if(removeNodeList.isEmpty()) {
                 return R.fail("删除失败,该节点下有已填报的资料");
@@ -3693,6 +3683,7 @@ public R removeContractTreeNodeJudge(@RequestParam String ids) {
             this.recycleBinService.save(new RecycleBin(String.join(",", idArray), "工程划分批量删除", 2, positionStr, projectId, contractId));
             //改为物理删除 (8.28改为逻辑删除,方便恢复)
             this.wbsTreeContractClient.removeContractTreeNode(idArray);
+            this.recycleBinInfoService.saveRecycleBinInfoByWbsTreeContract(String.join(",", idArray),ids);
             //更新redis
             this.informationQueryService.delAsyncWbsTree(removeWbsTreeContracts.get(0).getContractId());
             this.wbsTreeContractStatisticsClient.delWbsTreeContractNodes(String.join(",", idArray));
@@ -4198,7 +4189,7 @@ public R<Boolean> saveContractTreeNode(@RequestBody AddContractTreeNodeVO vo) {
             }
         }
         //TODO 20250414-lhb-新增 添加ancestorsPId字段
-        attachNodesToTarget(saveList,treeContract.getPKeyId(),treeContract.getAncestorsPId());
+        attachNodesToTarget(saveList);
 
         R<Boolean> booleanR = this.saveOrCopyNodeTree(saveList, saveLedger, 2, treeContract);
 
@@ -5114,15 +5105,18 @@ public R<Object> customAddContractNode(@RequestBody CustomAddContractNodeDTO dto
         }
     }
 
-    public void attachNodesToTarget(List<WbsTreeContract> newNodes, Long targetId, String targetAncestors) {
+    public void attachNodesToTarget(List<WbsTreeContract> newNodes) {
         // 1. 找到新数据中的顶层节点(新树的根节点)
         List<WbsTreeContract> newRoot = findRootNode(newNodes);
 
         // 2. 将新树的根节点绑定到目标节点
         newRoot.forEach(f -> {
-            f.setPId(targetId);
-            f.setAncestorsPId(calculateAncestors(targetAncestors, targetId));
-
+            WbsTreeContract parentNode = this.wbsTreeContractClient.getContractNodeByPrimaryKeyId(String.valueOf(f.getPId()));
+            if(parentNode ==  null){
+                f.setAncestorsPId("0");
+            }else{
+                f.setAncestorsPId(calculateAncestors(parentNode.getAncestorsPId(), f.getPId()));
+            }
 
             // 3. 构建映射关系
             Map<Long, WbsTreeContract> nodeMap = new HashMap<>();

+ 11 - 4
blade-service/blade-business/src/main/java/org/springblade/business/controller/MaterialProgressController.java

@@ -524,22 +524,22 @@ public class MaterialProgressController extends BladeController {
         //工序资料
         long processAmount = 0L;
         if (process.size() > 0) {
-            processAmount = this.informationQueryService.count(Wrappers.<InformationQuery>lambdaQuery().in(InformationQuery::getWbsId, process).eq(InformationQuery::getClassify, classifyType));
+            processAmount = this.informationQueryService.count(Wrappers.<InformationQuery>query().select("distinct wbs_id").in("wbs_id", process).eq("classify", classifyType));
         }
         //开工报告
         long workStartReportsAmount = 0L;
         if (workStartReports.size() > 0) {
-            workStartReportsAmount = this.informationQueryService.count(Wrappers.<InformationQuery>lambdaQuery().in(InformationQuery::getWbsId, workStartReports).eq(InformationQuery::getClassify, classifyType));
+            workStartReportsAmount = this.informationQueryService.count(Wrappers.<InformationQuery>query().select("distinct wbs_id").in("wbs_id", workStartReports).eq("classify", classifyType));
         }
         //质量评定
         long evaluationAmount = 0L;
         if (evaluation.size() > 0) {
-            evaluationAmount = this.informationQueryService.count(Wrappers.<InformationQuery>lambdaQuery().in(InformationQuery::getWbsId, evaluation).eq(InformationQuery::getClassify, classifyType));
+            evaluationAmount = this.informationQueryService.count(Wrappers.<InformationQuery>query().select("distinct wbs_id").in("wbs_id", evaluation).eq("classify", classifyType));
         }
         //中间交工
         long completionAmount = 0L;
         if (completion.size() > 0) {
-            completionAmount = this.informationQueryService.count(Wrappers.<InformationQuery>lambdaQuery().in(InformationQuery::getWbsId, completion).eq(InformationQuery::getClassify, classifyType));
+            completionAmount = this.informationQueryService.count(Wrappers.<InformationQuery>query().select("distinct wbs_id").in("wbs_id", completion).eq("classify", classifyType));
         }
 
         reVO.setProcessMaterialList("开工报告", workStartReports.size(), workStartReportsAmount);
@@ -556,6 +556,13 @@ public class MaterialProgressController extends BladeController {
         AtomicInteger await = new AtomicInteger();
         //已审批
         AtomicInteger approval = new AtomicInteger();
+        if (list != null && !list.isEmpty()) {
+            Map<Long, InformationQuery> map = list.stream().collect(Collectors.toMap(InformationQuery::getWbsId, item -> item, (v1, v2) -> v1.getId() > v2.getId() ? v1 : v2));
+            list = new ArrayList<>(map.values());
+        }
+        if (list ==  null) {
+            list = new ArrayList<>();
+        }
         list.forEach(vo -> {
             switch (vo.getStatus()) {
                 case 1:

+ 110 - 0
blade-service/blade-business/src/main/java/org/springblade/business/controller/RecycleBinController.java

@@ -10,25 +10,37 @@ import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import lombok.AllArgsConstructor;
 
 import org.apache.commons.lang.StringUtils;
+import org.springblade.business.dto.RecycleBinInfoDTO;
 import org.springblade.business.entity.ArchiveFile;
 import org.springblade.business.entity.ImageClassificationFile;
+import org.springblade.business.entity.RecycleBinInfo;
 import org.springblade.business.feign.InformationQueryClient;
 import org.springblade.business.feignClient.WbsTreeContractStatisticsClientImpl;
 import org.springblade.business.service.IArchiveFileService;
 import org.springblade.business.service.IImageClassificationFileService;
+import org.springblade.business.service.IRecycleBinInfoService;
+import org.springblade.business.vo.RecycleBinInfoVO;
 import org.springblade.business.vo.RecycleBinVO;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
+import org.springblade.core.redis.cache.BladeRedis;
 import org.springblade.core.secure.utils.AuthUtil;
 import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.ObjectUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.manager.entity.WbsTreeContract;
 import org.springblade.manager.feign.WbsTreeContractClient;
+import org.springblade.system.user.vo.UserVO2;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.web.bind.annotation.*;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.springblade.business.entity.RecycleBin;
 import org.springblade.business.service.IRecycleBinService;
 import org.springblade.core.boot.ctrl.BladeController;
 
+import javax.validation.Valid;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -55,6 +67,9 @@ public class RecycleBinController extends BladeController {
     private final InformationQueryClient informationQueryClient;
 
     private final WbsTreeContractStatisticsClientImpl wbsTreeContractStatisticsClient;
+    private final IRecycleBinInfoService recycleBinInfoService;
+    private final BladeRedis bladeRedis;
+    private final JdbcTemplate jdbcTemplate;
 
     /**
      * 恢复
@@ -168,4 +183,99 @@ public class RecycleBinController extends BladeController {
         return R.data(this.recycleBinService.page(Condition.getPage(query), wrapper));
     }
 
+    @GetMapping("/page")
+    @ApiOperationSupport(order = 1)
+    @ApiOperation(value = "分页")
+    public R<IPage<RecycleBinInfo>> page(@Valid RecycleBinInfoVO vo) {
+        return R.data(this.recycleBinInfoService.selectPage(vo));
+    }
+
+    /**
+     * 恢复
+     */
+    @PostMapping("/recover")
+    @ApiOperationSupport(order = 2)
+    @ApiOperation(value = "恢复(工程划分)")
+    public R<Boolean> recover(@RequestBody RecycleBinInfoDTO dto) {
+        if (StringUtil.hasText(dto.getIds())) {
+            //获取数据
+            String[] ids = dto.getIds().split(",");
+            List<Long> collect = Arrays.stream(ids).filter(StringUtils::isNumeric).map(Long::parseLong).collect(Collectors.toList());
+            if (collect.isEmpty()) {
+                return R.data(true);
+            }
+            List<RecycleBinInfo> recycleBinInfoList = this.recycleBinInfoService.list(Wrappers.<RecycleBinInfo>lambdaQuery().in(RecycleBinInfo::getId, collect).eq(RecycleBinInfo::getStatus, 0).eq(RecycleBinInfo::getDelType, 2));
+            if (dto.getRecoverOperationData() == 1) {
+                Set<Long> set = recycleBinInfoList.stream().map(RecycleBinInfo::getDelRootId).collect(Collectors.toSet());
+                recycleBinInfoList = this.recycleBinInfoService.list(Wrappers.<RecycleBinInfo>lambdaQuery().in(RecycleBinInfo::getDelRootId, set)
+                                .eq(RecycleBinInfo::getDelType, 2).eq(RecycleBinInfo::getStatus, 0));
+            }
+            boolean regainNode = false;
+            if (!recycleBinInfoList.isEmpty()) {
+                List<String> processNodeList = new ArrayList<>();
+                try {
+                    //恢复数据
+                    Set<Long> parentIds = new HashSet<>();
+                    for (RecycleBinInfo info : recycleBinInfoList) {
+                        String position = info.getBusinessId();
+                        if (position ==  null) {
+                            continue;
+                        }
+                        String[] split = position.split(",");
+                        for (String s : split) {
+                            if (StringUtil.isNumeric(s) && !s.equals("0")) {
+                                parentIds.add(Long.parseLong(s));
+                            }
+                        }
+                    }
+                    if (!parentIds.isEmpty()) {
+                        List<RecycleBinInfo> list = this.recycleBinInfoService.list(Wrappers.<RecycleBinInfo>lambdaQuery().in(RecycleBinInfo::getDelId, parentIds).eq(RecycleBinInfo::getDelType, 2).eq(RecycleBinInfo::getStatus, 0));
+                        recycleBinInfoList.addAll(list);
+                    }
+                    processNodeList = recycleBinInfoList.stream().map(item -> item.getDelId() + "").collect(Collectors.toList());
+                    regainNode = this.wbsTreeContractClient.regainRemoveTreeByPrimaryKeyIds(processNodeList);
+                    List<WbsTreeContract> query = jdbcTemplate.query("SELECT p_key_id  FROM m_wbs_tree_contract a WHERE  ( SELECT count(1) FROM m_wbs_tree_contract WHERE contract_id = a.contract_id and type = 1 AND p_id = a.p_id AND full_name = a.full_name AND p_key_id != a.p_key_id AND is_deleted = 0 ) > 0 " +
+                            "AND is_deleted = 0 and a.p_key_id in ( " + String.join(",", processNodeList) + ")", new BeanPropertyRowMapper<>(WbsTreeContract.class));
+                    if (!query.isEmpty()) {
+                        String collect1 = query.stream().map(item -> item.getPKeyId() + "").collect(Collectors.joining(","));
+                        jdbcTemplate.execute("update m_wbs_tree_contract set full_name = CONCAT( full_name, '(恢)' ) where p_key_id in (" + collect1 + ")");
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+                informationQueryClient.delAsyncWbsTree(recycleBinInfoList.get(0).getContractId() + "");
+                wbsTreeContractStatisticsClient.recycleWbsTreeContractNodes(String.join(",", processNodeList));
+            }
+            //删除回收站记录
+            if (!recycleBinInfoList.isEmpty() && regainNode) {
+                List<Long> recycleBinIds = recycleBinInfoList.stream().map(RecycleBinInfo::getId).collect(Collectors.toList());
+                this.recycleBinInfoService.update(Wrappers.<RecycleBinInfo>lambdaUpdate().set(RecycleBinInfo::getStatus, 1).set(RecycleBinInfo::getUpdateTime, new Date())
+                        .set(RecycleBinInfo::getUpdateUser, AuthUtil.getUserId()).set(RecycleBinInfo::getUpdateUserName, AuthUtil.getNickName()).in(RecycleBinInfo::getId, recycleBinIds));
+                Set<Long> set = recycleBinInfoList.stream().map(RecycleBinInfo::getContractId).collect(Collectors.toSet());
+                for (Long l : set) {
+                    bladeRedis.del("blade:recycle:user:cache:" + l, "blade:recycle:user:cache:" + l + "_1");
+                }
+                return R.data(true);
+            } else {
+                return R.data(300, false, "数据恢复失败,请联系维护人员处理");
+            }
+        }
+        return R.data(300, false, "未找到需要需要恢复的数据");
+    }
+
+    @GetMapping("/queryUser")
+    @ApiOperationSupport(order = 1)
+    @ApiOperation(value = "查询用户")
+    public R<Collection<UserVO2>> queryUser(@RequestParam Long contractId, @RequestParam(required = false, defaultValue = "0") Integer isRecycleBin, @RequestParam(required = false) String name) {
+        return R.data(this.recycleBinInfoService.queryUser(contractId,isRecycleBin, name));
+    }
+
+
+    @GetMapping("/queryOperation")
+    @ApiOperationSupport(order = 1)
+    @ApiOperation(value = "查询同一批删除数据")
+    public R<Collection<RecycleBinInfo>> queryOperation(@RequestParam String ids) {
+        return R.data(this.recycleBinInfoService.queryOperation(ids));
+    }
+
 }

+ 17 - 0
blade-service/blade-business/src/main/java/org/springblade/business/mapper/RecycleBinInfoMapper.java

@@ -0,0 +1,17 @@
+package org.springblade.business.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.ibatis.annotations.Param;
+import org.springblade.business.entity.RecycleBinInfo;
+import org.springblade.business.vo.RecycleBinInfoVO;
+
+import java.util.Collection;
+
+public interface RecycleBinInfoMapper extends BaseMapper<RecycleBinInfo> {
+
+
+    IPage<RecycleBinInfo> page(IPage<RecycleBinInfo> iPage, @Param("vo") RecycleBinInfoVO recycleBinInfoVO);
+
+    Collection<RecycleBinInfo> queryOperation(Collection<Long> ids);
+}

+ 95 - 0
blade-service/blade-business/src/main/java/org/springblade/business/mapper/RecycleBinInfoMapper.xml

@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.springblade.business.mapper.RecycleBinInfoMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="recycleBinResultMap" type="org.springblade.business.entity.RecycleBinInfo">
+        <result column="id" property="id"/>
+        <result column="create_user" property="createUser"/>
+        <result column="create_dept" property="createDept"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_user" property="updateUser"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="status" property="status"/>
+        <result column="is_deleted" property="isDeleted"/>
+        <result column="project_id" property="projectId"/>
+        <result column="contract_id" property="contractId"/>
+        <result column="del_type" property="delType"/>
+        <result column="file_name" property="fileName"/>
+        <result column="position" property="position"/>
+        <result column="business_id" property="businessId"/>
+        <result column="operation_time" property="operationTime"/>
+        <result column="create_user_name" property="createUserName"/>
+        <result column="update_user_name" property="updateUserName"/>
+        <result column="del_id" property="delId"/>
+        <result column="del_root_id" property="delRootId"/>
+        <result column="del_root_name" property="delRootName"/>
+        <result column="operation_id" property="operationId"/>
+        <result column="jl_file_name" property="jlFileName"/>
+        <result column="is_data" property="isData"/>
+    </resultMap>
+
+    <sql id="includeSql" >
+        id, create_user, create_dept, create_time, update_user, update_time, status, is_deleted, project_id, contract_id, del_type, file_name, position, business_id, operation_time, create_user_name, update_user_name, del_id, del_root_id, del_root_name, operation_id, jl_file_name, is_data
+    </sql>
+    <select id="page" resultMap="recycleBinResultMap">
+        select
+        <include refid="includeSql"/>
+        from u_recycle_bin_info
+        where is_deleted = 0
+        <if test="vo.contractId != null">
+            and contract_id = #{vo.contractId}
+        </if>
+        <if test="vo.delType != null">
+            and del_type = #{vo.delType}
+        </if>
+        <if test="vo.isData != null">
+            and is_data = #{vo.isData}
+        </if>
+        <if test="vo.recycleType != null">
+            and status = #{vo.recycleType}
+            <if test="vo.recycleType == 1">
+                <if test="vo.startTime != null">
+                    and create_time >= #{vo.startTime}
+                </if>
+                <if test="vo.endTime != null">
+                    and create_time &lt;= #{vo.endTime}
+                </if>
+                <if test="vo.userId != null">
+                    and create_user = #{vo.userId}
+                </if>
+            </if>
+            <if test="vo.recycleType == 0">
+                <if test="vo.startTime != null">
+                    and update_time >= #{vo.startTime}
+                </if>
+                <if test="vo.endTime != null">
+                    and update_time &lt;= #{vo.endTime}
+                </if>
+                <if test="vo.userId != null">
+                    and update_user = #{vo.userId}
+                </if>
+            </if>
+        </if>
+        <if test="vo.content != null and vo.content != ''">
+            and (file_name like concat('%',#{vo.content},'%') or jl_file_name like concat('%',#{vo.content},'%') or del_root_name like concat('%',#{vo.content},'%') or position like concat('%',#{vo.content},'%'))
+        </if>
+        <if test="vo.operationId != null">
+            and operation_id = #{vo.operationId}
+        </if>
+        order by
+        <if test="vo.recycleType == 1">
+            create_time desc
+        </if>
+        <if test="vo.recycleType == 0">
+            update_time desc
+        </if>
+    </select>
+    <select id="queryOperation" resultType="org.springblade.business.entity.RecycleBinInfo">
+        select del_root_id,del_root_name from u_recycle_bin_info where del_root_id in ( select del_root_id from u_recycle_bin_info where id in (
+            <foreach collection="ids" item="id" separator=",">
+                #{id}
+            </foreach>
+            ) and is_deleted = 0 group by del_root_id ) and status = 0  group by del_root_id, del_root_name having count(1) > 1
+    </select>
+</mapper>

+ 25 - 0
blade-service/blade-business/src/main/java/org/springblade/business/service/IRecycleBinInfoService.java

@@ -0,0 +1,25 @@
+package org.springblade.business.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.business.entity.RecycleBinInfo;
+import org.springblade.business.vo.RecycleBinInfoVO;
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.system.user.vo.UserVO2;
+
+import java.util.Collection;
+import java.util.List;
+
+
+public interface IRecycleBinInfoService extends BaseService<RecycleBinInfo> {
+
+    /**
+     * 分页
+     */
+    IPage<RecycleBinInfo> selectPage(RecycleBinInfoVO recycleBinInfoVO);
+
+    Collection<UserVO2> queryUser(Long contractId, Integer isRecycleBin, String name);
+
+    Collection<RecycleBinInfo> queryOperation(String ids);
+
+    Boolean saveRecycleBinInfoByWbsTreeContract(String ids, String rootIds);
+}

+ 394 - 0
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/RecycleBinInfoServiceImpl.java

@@ -0,0 +1,394 @@
+package org.springblade.business.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.Nullable;
+import org.springblade.business.entity.InformationQuery;
+import org.springblade.business.entity.RecycleBin;
+import org.springblade.business.entity.RecycleBinInfo;
+import org.springblade.business.mapper.RecycleBinInfoMapper;
+import org.springblade.business.mapper.RecycleBinMapper;
+import org.springblade.business.service.IInformationQueryService;
+import org.springblade.business.service.IRecycleBinInfoService;
+import org.springblade.business.vo.RecycleBinInfoVO;
+import org.springblade.common.utils.SnowFlakeUtil;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.manager.entity.ContractInfo;
+import org.springblade.manager.entity.WbsTreeContract;
+import org.springblade.system.user.entity.User;
+import org.springblade.system.user.vo.UserVO2;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class RecycleBinInfoServiceImpl extends BaseServiceImpl<RecycleBinInfoMapper, RecycleBinInfo> implements IRecycleBinInfoService {
+
+    private final RecycleBinInfoMapper recycleBinInfoMapper;
+
+    private final JdbcTemplate jdbcTemplate;
+
+    private final BladeRedis bladeRedis;
+
+    private final IInformationQueryService informationQueryService;
+
+    private final static String BLADE_RECYCLE_USER_CACHE_KEY = "blade:recycle:user:cache:";
+
+    @Override
+    public IPage<RecycleBinInfo> selectPage(RecycleBinInfoVO recycleBinInfoVO) {
+        IPage<RecycleBinInfo> iPage = Condition.getPage(recycleBinInfoVO);
+        if (recycleBinInfoVO.getStartTime() != null) {
+            if (recycleBinInfoVO.getStartTime().trim().matches("^\\d{4}-\\d{2}-\\d{2}$")) {
+                recycleBinInfoVO.setStartTime(recycleBinInfoVO.getStartTime().trim() + " 00:00:00");
+            } else {
+                recycleBinInfoVO.setStartTime(null);
+            }
+        }
+        if (recycleBinInfoVO.getEndTime() != null) {
+            if (recycleBinInfoVO.getEndTime().trim().matches("^\\d{4}-\\d{2}-\\d{2}$")) {
+                recycleBinInfoVO.setEndTime(recycleBinInfoVO.getEndTime().trim() + " 23:59:59");
+            } else {
+                recycleBinInfoVO.setEndTime(null);
+            }
+        }
+        return this.recycleBinInfoMapper.page(iPage, recycleBinInfoVO);
+    }
+
+    @Override
+    public Collection<UserVO2> queryUser(Long contractId, Integer isRecycleBin, String name) {
+        String str = bladeRedis.get(BLADE_RECYCLE_USER_CACHE_KEY + contractId);
+        List<UserVO2> userVOs = getUserVOs(str);
+        List<UserVO2> userVOs1;
+        if (isRecycleBin == 1) {
+            userVOs1 = getUpdateUser(contractId);
+        } else {
+            userVOs1 = new ArrayList<>();
+        }
+        if (userVOs.isEmpty()) {
+            List<RecycleBinInfo> list = this.list(Wrappers.<RecycleBinInfo>lambdaQuery()
+                    .select(RecycleBinInfo::getCreateUser, RecycleBinInfo::getCreateUserName)
+                    .eq(RecycleBinInfo::getContractId, contractId).eq(RecycleBinInfo::getDelType, 2).eq(RecycleBinInfo::getStatus, 0)
+                    .groupBy(Arrays.asList(RecycleBinInfo::getCreateUser,RecycleBinInfo::getCreateUserName)));
+            if (list != null && !list.isEmpty()) {
+                list.forEach(item -> {
+                    UserVO2 vo2 = new UserVO2();
+                    vo2.setId(item.getCreateUser());
+                    vo2.setName(item.getCreateUserName());
+                    userVOs.add(vo2);
+                });
+                bladeRedis.set(BLADE_RECYCLE_USER_CACHE_KEY + contractId, JSON.toJSONString(userVOs));
+                bladeRedis.expire(BLADE_RECYCLE_USER_CACHE_KEY + contractId, 60 * 60);
+            }
+        }
+        if (!userVOs.isEmpty()) {
+            if (name != null && !name.isEmpty()) {
+                List<UserVO2> collect = userVOs.stream().filter(item -> item.getName() != null && item.getName().contains(name)).collect(Collectors.toList());
+                Set<UserVO2> set = new HashSet<>(collect);
+                set.addAll(userVOs1.stream().filter(item -> item.getName() != null && item.getName().contains(name)).collect(Collectors.toList()));
+                return set;
+            }
+            Set<UserVO2> set = new HashSet<>(userVOs);
+            set.addAll(userVOs1);
+            return set;
+        }
+        return userVOs1;
+    }
+
+    @Override
+    public Collection<RecycleBinInfo> queryOperation(String ids) {
+        if (ids == null || ids.isEmpty()) {
+            return new ArrayList<>();
+        }
+        String[] split = ids.split(",");
+        Set<Long> set = Arrays.stream(split).filter(StringUtil::isNumeric).map(Long::parseLong).collect(Collectors.toSet());
+        if (set.isEmpty()) {
+            return new ArrayList<>();
+        }
+        return this.baseMapper.queryOperation(set);
+    }
+
+    @Override
+    public Boolean saveRecycleBinInfoByWbsTreeContract(String ids, String rootIds) {
+        if (ids == null || ids.isEmpty()) {
+            return true;
+        }
+        List<WbsTreeContract> query = jdbcTemplate.query("select * from m_wbs_tree_contract where p_key_id in ( " + ids + ") and is_deleted = 1 and type = 1", new BeanPropertyRowMapper<>(WbsTreeContract.class));
+        if (query.isEmpty()) {
+            return true;
+        }
+
+        List<String> parentIds = new ArrayList<>();
+        Map<Long, WbsTreeContract> rootMap = new HashMap<>();
+        {
+            Map<Long, WbsTreeContract> tempMap = query.stream().collect(Collectors.toMap(WbsTreeContract::getPKeyId, item -> item));
+            for (WbsTreeContract contract : query) {
+                String ancestorsPId = contract.getAncestorsPId();
+                if (StringUtil.hasText(ancestorsPId)) {
+                    String[] split = ancestorsPId.split(",");
+                    WbsTreeContract root = null;
+                    for (String s : split) {
+                        if (StringUtil.isNumeric(s)) {
+                            parentIds.add(s);
+                            WbsTreeContract wbsTreeContract = tempMap.get(Long.parseLong(s));
+                            if (root == null && wbsTreeContract != null) {
+                                root = wbsTreeContract;
+                            }
+                        }
+                    }
+                    if (root != null) {
+                        rootMap.put(contract.getPKeyId(), root);
+                    }
+                }
+            }
+        }
+        List<WbsTreeContract> parentContracts;
+        if (!parentIds.isEmpty()) {
+            parentContracts = jdbcTemplate.query("select * from m_wbs_tree_contract where p_key_id in ( " + String.join(",", parentIds) + ") and is_deleted = 0 and type = 1", new BeanPropertyRowMapper<>(WbsTreeContract.class));
+        } else {
+            parentContracts = new ArrayList<>();
+        }
+
+        Map<Long, WbsTreeContract> parentMap = parentContracts.stream().collect(Collectors.toMap(WbsTreeContract::getPKeyId, item -> item));
+        String contractId = query.get(0).getContractId();
+        List<InformationQuery> queries = informationQueryService.list(Wrappers.<InformationQuery>lambdaQuery().select(InformationQuery::getWbsId, InformationQuery::getClassify, InformationQuery::getName)
+                .eq(InformationQuery::getContractId, contractId).in(InformationQuery::getWbsId, ids).in(InformationQuery::getClassify, 1, 2));
+        Map<String, List<InformationQuery>> map = queries.stream().collect(Collectors.groupingBy(item -> item.getWbsId() + "_" + item.getClassify()));
+        List<ContractInfo> contractInfos = jdbcTemplate.query("select * from m_contract_info where id = ( " + contractId + ") and is_deleted = 0", new BeanPropertyRowMapper<>(ContractInfo.class));
+        ContractInfo contractInfo;
+        if (contractInfos.isEmpty()) {
+            contractInfo = new ContractInfo();
+        } else {
+            contractInfo = contractInfos.get(0);
+        }
+        long operationId = SnowFlakeUtil.getId();
+        List<RecycleBinInfo> recycleBinInfos = new ArrayList<>(query.size());
+        for (WbsTreeContract contract : query) {
+            RecycleBinInfo info = new RecycleBinInfo();
+            info.setId(SnowFlakeUtil.getId());
+            info.setContractId(Long.parseLong(contract.getContractId()));
+            info.setProjectId(Long.parseLong(contract.getProjectId()));
+            info.setOperationId(operationId + "");
+            info.setDelType(2);
+            info.setCreateUserName(AuthUtil.getNickName());
+            info.setCreateUser(AuthUtil.getUserId());
+            info.setOperationTime(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
+            info.setDelId(contract.getPKeyId());
+            info.setBusinessId(contract.getAncestorsPId());
+
+            WbsTreeContract delRoot = rootMap.get(contract.getPKeyId());
+            if (delRoot != null) {
+                info.setDelRootId(delRoot.getPKeyId());
+                info.setDelRootName(StringUtil.hasText(delRoot.getFullName()) ? delRoot.getFullName() : delRoot.getNodeName());
+            }
+            String ancestorsPId = contract.getAncestorsPId();
+            StringBuilder sb = new StringBuilder();
+            if (StringUtil.hasText(ancestorsPId)) {
+                String[] split = ancestorsPId.split(",");
+                for (String s : split) {
+                    if (StringUtil.isNumeric(s)) {
+                        WbsTreeContract parent = parentMap.get(Long.parseLong(s));
+                        if (parent != null) {
+                            if (parent.getPId() == null || parent.getPId() == 0) {
+                                sb.append(contractInfo.getContractName()).append("/");
+                            } else {
+                                sb.append(StringUtil.hasText(parent.getFullName()) ? parent.getFullName() : parent.getNodeName()).append("/");
+                            }
+                            if (info.getDelRootId() == null) {
+                                WbsTreeContract temp = rootMap.get(parent.getPKeyId());
+                                if (temp != null) {
+                                    info.setDelRootId(temp.getPKeyId());
+                                    info.setDelRootName(StringUtil.hasText(temp.getFullName()) ? temp.getFullName() : temp.getNodeName());
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            if (info.getDelRootId() == null) {
+                info.setDelRootId(info.getDelId());
+                info.setDelRootName(StringUtil.hasText(contract.getFullName()) ? contract.getFullName() : contract.getNodeName());
+            }
+            info.setPosition(sb + (StringUtil.hasText(contract.getFullName()) ? contract.getFullName() : contract.getNodeName()));
+            List<InformationQuery> queryList = map.get(contract.getPKeyId() + "_1");
+            if (queryList != null && !queryList.isEmpty()) {
+                info.setFileName(queryList.get(0).getName());
+                info.setIsData(1);
+            }
+            queryList = map.get(contract.getPKeyId() + "_2");
+            if (queryList != null && !queryList.isEmpty()) {
+                info.setJlFileName(queryList.get(0).getName());
+                info.setIsData(1);
+            }
+            recycleBinInfos.add( info);
+        }
+        this.saveBatch(recycleBinInfos);
+        bladeRedis.del(BLADE_RECYCLE_USER_CACHE_KEY + contractId + "_1", BLADE_RECYCLE_USER_CACHE_KEY + contractId);
+        return true;
+    }
+
+    private List<UserVO2> getUpdateUser(Long contractId) {
+        String key = BLADE_RECYCLE_USER_CACHE_KEY + contractId + "_1";
+        String str1 = bladeRedis.get(key);
+        List<UserVO2> userVOs1 = getUserVOs(str1);
+        if (!userVOs1.isEmpty()) {
+            return userVOs1;
+        }
+        List<RecycleBinInfo> list = this.list(Wrappers.<RecycleBinInfo>lambdaQuery()
+                .select(RecycleBinInfo::getUpdateUser, RecycleBinInfo::getUpdateUserName)
+                .eq(RecycleBinInfo::getContractId, contractId).eq(RecycleBinInfo::getDelType, 2).eq(RecycleBinInfo::getStatus, 1)
+                .groupBy(Arrays.asList(RecycleBinInfo::getUpdateUser,RecycleBinInfo::getUpdateUserName)));
+        if (list != null && !list.isEmpty()) {
+            list.forEach(item -> {
+                UserVO2 vo2 = new UserVO2();
+                vo2.setId(item.getUpdateUser());
+                vo2.setName(item.getUpdateUserName());
+                userVOs1.add(vo2);
+            });
+            bladeRedis.set(key, JSON.toJSONString(userVOs1));
+            bladeRedis.expire(key, 60 * 60);
+        }
+        return userVOs1;
+    }
+    private List<UserVO2> getUserVOs(String str) {
+        if (str != null) {
+            try {
+                List<UserVO2> userVO2s = JSON.parseArray(str, UserVO2.class);
+                if (userVO2s != null && !userVO2s.isEmpty()) {
+                    return userVO2s;
+                }
+            } catch (Exception e) {
+                log.warn("从缓存中获取用户信息失败,error msg:" + e.getMessage());
+            }
+        }
+        return new ArrayList<>();
+    }
+
+    public void sync() {
+        List<ContractInfo> contractInfos = jdbcTemplate.query("select id,contract_name from m_contract_info where is_deleted = 0 and p_id in (select id from m_project_info where is_deleted = 0)", new BeanPropertyRowMapper<>(ContractInfo.class));
+        List<User> users = jdbcTemplate.query("select * from blade_user", new BeanPropertyRowMapper<>(User.class));
+        Map<Long, User> userMap = users.stream().collect(Collectors.toMap(User::getId, item -> item));
+        for (ContractInfo contractInfo : contractInfos) {
+            List<RecycleBin> recycleBins = jdbcTemplate.query("select * from u_recycle_bin where del_type = 2 and contract_id = " + contractInfo.getId(), new BeanPropertyRowMapper<>(RecycleBin.class));
+            if (recycleBins.isEmpty()) {
+                continue;
+            }
+            Map<Long, WbsTreeContract> treeContractMap = new TreeMap<>();
+            for (RecycleBin recycleBin : recycleBins) {
+                String businessId = recycleBin.getBusinessId();
+                String[] split1 = businessId.split(",");
+                String ids = Arrays.stream(split1).filter(StringUtil::isNumeric).collect(Collectors.joining(","));
+                if (StringUtil.isEmpty(ids)) {
+                    continue;
+                }
+                List<WbsTreeContract> query = jdbcTemplate.query("select * from m_wbs_tree_contract where p_key_id in ( " + ids + ") and type = 1", new BeanPropertyRowMapper<>(WbsTreeContract.class));
+                if (query.isEmpty()) {
+                    continue;
+                }
+                List<String> parentIds = new ArrayList<>();
+                for (WbsTreeContract contract : query) {
+                    String ancestorsPId = contract.getAncestorsPId();
+                    if (StringUtil.hasText(ancestorsPId)) {
+                        String[] split = ancestorsPId.split(",");
+                        for (String s : split) {
+                            if (StringUtil.isNumeric(s)) {
+                                long l = Long.parseLong(s);
+                                if (treeContractMap.containsKey(l)) {
+                                    continue;
+                                }
+                                parentIds.add(s);
+                            }
+                        }
+                    }
+                    treeContractMap.put(contract.getPKeyId(), contract);
+                }
+                List<WbsTreeContract> parentContracts;
+                if (!parentIds.isEmpty()) {
+                    parentContracts = jdbcTemplate.query("select * from m_wbs_tree_contract where p_key_id in ( " + String.join(",", parentIds) + ") and is_deleted = 0 and type = 1", new BeanPropertyRowMapper<>(WbsTreeContract.class));
+                } else {
+                    parentContracts = new ArrayList<>();
+                }
+                if (!parentContracts.isEmpty()) {
+                    parentContracts.forEach(item -> treeContractMap.put(item.getPKeyId(), item));
+                }
+                List<InformationQuery> queries = informationQueryService.list(Wrappers.<InformationQuery>lambdaQuery().select(InformationQuery::getWbsId, InformationQuery::getClassify, InformationQuery::getName)
+                        .eq(InformationQuery::getContractId, contractInfo.getId()).in(InformationQuery::getWbsId, ids).in(InformationQuery::getClassify, 1, 2));
+                Map<String, List<InformationQuery>> map = queries.stream().collect(Collectors.groupingBy(item -> item.getWbsId() + "_" + item.getClassify()));
+                long operationId = SnowFlakeUtil.getId();
+                List<RecycleBinInfo> recycleBinInfos = new ArrayList<>(query.size());
+                for (WbsTreeContract contract : query) {
+                    RecycleBinInfo info = new RecycleBinInfo();
+                    info.setId(SnowFlakeUtil.getId());
+                    info.setContractId(Long.parseLong(contract.getContractId()));
+                    info.setProjectId(Long.parseLong(contract.getProjectId()));
+                    info.setOperationId(operationId + "");
+                    info.setDelType(2);
+                    info.setCreateUserName(recycleBin.getCreateUserName());
+                    info.setCreateUser(recycleBin.getCreateUser());
+                    info.setCreateTime(recycleBin.getCreateTime());
+                    info.setOperationTime(recycleBin.getOperationTime());
+                    info.setDelId(contract.getPKeyId());
+                    info.setBusinessId(contract.getAncestorsPId());
+                    if (recycleBin.getIsDeleted() == 1) {
+                        info.setStatus(1);
+                        info.setUpdateUser(recycleBin.getUpdateUser());
+                        info.setUpdateTime(recycleBin.getUpdateTime());
+                        User user = userMap.get(recycleBin.getUpdateUser());
+                        info.setUpdateUserName(user.getName());
+                        info.setUpdateTime(recycleBin.getUpdateTime());
+                    }
+
+                    String ancestorsPId = contract.getAncestorsPId();
+                    StringBuilder sb = new StringBuilder();
+                    if (StringUtil.hasText(ancestorsPId)) {
+                        String[] split = ancestorsPId.split(",");
+                        for (String s : split) {
+                            if (StringUtil.isNumeric(s)) {
+                                WbsTreeContract parent = treeContractMap.get(Long.parseLong(s));
+                                if (parent != null) {
+                                    if (parent.getPId() == null || parent.getPId() == 0) {
+                                        sb.append(contractInfo.getContractName()).append("/");
+                                    } else {
+                                        sb.append(StringUtil.hasText(parent.getFullName()) ? parent.getFullName() : parent.getNodeName()).append("/");
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    if (info.getDelRootId() == null) {
+                        info.setDelRootId(info.getDelId());
+                        info.setDelRootName(StringUtil.hasText(contract.getFullName()) ? contract.getFullName() : contract.getNodeName());
+                    }
+                    info.setPosition(sb + (StringUtil.hasText(contract.getFullName()) ? contract.getFullName() : contract.getNodeName()));
+                    List<InformationQuery> queryList = map.get(contract.getPKeyId() + "_1");
+                    if (queryList != null && !queryList.isEmpty()) {
+                        info.setFileName(queryList.get(0).getName());
+                        info.setIsData(1);
+                    }
+                    queryList = map.get(contract.getPKeyId() + "_2");
+                    if (queryList != null && !queryList.isEmpty()) {
+                        info.setJlFileName(queryList.get(0).getName());
+                        info.setIsData(1);
+                    }
+                    recycleBinInfos.add( info);
+                }
+                this.saveBatch(recycleBinInfos);
+            }
+        }
+    }
+}

+ 1 - 1
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/TrialSelfInspectionRecordServiceImpl.java

@@ -2286,7 +2286,7 @@ public class TrialSelfInspectionRecordServiceImpl extends BaseServiceImpl<TrialS
             List<Long> collect = trialSampleInfoList.stream().map(TrialSampleInfo::getId).collect(Collectors.toList());
             recordDTO.setSampleIds(org.apache.commons.lang.StringUtils.join(collect, ","));
             recordDTO.setNodeId(dto.getNodeId());
-            recordDTO.setTrialUserName(AuthUtil.getUserName());
+            recordDTO.setTrialUserName(AuthUtil.getNickName());
             this.trialDeviceUseService.addDeviceUseInfo(recordDTO, record.getId());
         }
             if(dto.getOldRecordNumber()!=null&&dto.getOldRecordNumber().equals(dto.getRecordNo())){

+ 13 - 3
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/WbsTreeContractStatisticsServiceImpl.java

@@ -88,7 +88,7 @@ public class WbsTreeContractStatisticsServiceImpl extends ServiceImpl<WbsTreeCon
                     } else if (lock.getFillNum() == 1 && lock.getApproveNum() == 0 && lock.getCompleteNum() == 0) {
                         this.update(Wrappers.<WbsTreeContractStatistics>lambdaUpdate().in(WbsTreeContractStatistics::getId, collect).setSql("fill_num = fill_num - 1, approve_num = approve_num + 1"));
                     } else if (lock.getFillNum() == 0 && lock.getApproveNum() == 0 && lock.getCompleteNum() == 1) {
-                        this.update(Wrappers.<WbsTreeContractStatistics>lambdaUpdate().in(WbsTreeContractStatistics::getId, collect).setSql("fill_num = fill_num + 1, complete_num = complete_num - 1"));
+                        this.update(Wrappers.<WbsTreeContractStatistics>lambdaUpdate().in(WbsTreeContractStatistics::getId, collect).setSql("approve_num = approve_num + 1, complete_num = complete_num - 1"));
                     }
                 } else if (status == 2) {
                     this.update(Wrappers.<WbsTreeContractStatistics>lambdaUpdate().eq(WbsTreeContractStatistics::getId, wbsId)
@@ -121,7 +121,7 @@ public class WbsTreeContractStatisticsServiceImpl extends ServiceImpl<WbsTreeCon
                     } else if (lock.getJlFillNum() == 1 && lock.getJlApproveNum() == 0 && lock.getJlCompleteNum() == 0) {
                         this.update(Wrappers.<WbsTreeContractStatistics>lambdaUpdate().in(WbsTreeContractStatistics::getId, collect).setSql("jl_fill_num = jl_fill_num - 1, jl_approve_num = jl_approve_num + 1"));
                     } else if (lock.getJlFillNum() == 0 && lock.getJlApproveNum() == 0 && lock.getJlCompleteNum() == 1) {
-                        this.update(Wrappers.<WbsTreeContractStatistics>lambdaUpdate().in(WbsTreeContractStatistics::getId, collect).setSql("jl_approve_num = jl_approve_num - 1, jl_complete_num = jl_complete_num + 1"));
+                        this.update(Wrappers.<WbsTreeContractStatistics>lambdaUpdate().in(WbsTreeContractStatistics::getId, collect).setSql("jl_approve_num = jl_approve_num + 1, jl_complete_num = jl_complete_num - 1"));
                     }
                 } else if (status == 2) {
                     this.update(Wrappers.<WbsTreeContractStatistics>lambdaUpdate().eq(WbsTreeContractStatistics::getId, wbsId)
@@ -221,6 +221,16 @@ public class WbsTreeContractStatisticsServiceImpl extends ServiceImpl<WbsTreeCon
         return true;
     }
 
+    private void updateStatusAndIsDeleted(Long contractId) {
+        List<WbsTreeContractStatistics> query = jdbcTemplate.query("SELECT a.id,b.status,b.is_deleted from m_wbs_tree_contract_statistics a LEFT JOIN m_wbs_tree_contract b on a.id = b.p_key_id WHERE (a.status != b.status OR a.is_deleted != b.is_deleted) AND b.p_key_id IS NOT NULl and a.contract_id = " + contractId,
+                new BeanPropertyRowMapper<>(WbsTreeContractStatistics.class));
+        if (query.isEmpty()) {
+            return;
+        }
+        List<WbsTreeContractStatistics> list = query.stream().filter(item -> item.getStatus() != null && item.getIsDeleted() != null).collect(Collectors.toList());
+        this.updateBatchById(list);
+    }
+
     public Boolean updateLeafNum(String ids) {
         if (ids != null && !ids.isEmpty()) {
             String[] split = ids.split(",");
@@ -298,6 +308,7 @@ public class WbsTreeContractStatisticsServiceImpl extends ServiceImpl<WbsTreeCon
                 for (WbsTreeContractDto dto : contractDtoList) {
                     addWbsTreeContractStatistics.add(createWbsTreeContractStatistics(dto));
                 }
+                updateStatusAndIsDeleted(contractId);
             }
             // 统计叶子节点数量
 //            countLeafNum(addWbsTreeContractStatistics);
@@ -326,7 +337,6 @@ public class WbsTreeContractStatisticsServiceImpl extends ServiceImpl<WbsTreeCon
             log.error("更新合同段wbs树统计信息异常", e);
         }
     }
-
     private boolean updateWbsTreeContractStatisticsFields(WbsTreeContractStatistics old, WbsTreeContractStatistics newObj) {
         boolean result = false;
         if (ObjectUtil.nullSafeEquals(old.getParentId(), newObj.getParentId())) {

+ 16 - 15
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/WeatherInfoServiceImpl.java

@@ -120,7 +120,7 @@ public class WeatherInfoServiceImpl extends ServiceImpl<WeatherInfoMapper, Weath
     /**
      * 获取当前系统所有项目下所有合同段的当天天气
      */
-    @Scheduled(cron = "0 18 10 * * ?")
+    @Scheduled(cron = "0 10 0,6,12,18,23 * * ?")
     public void syncWeatherInfo() {
         if (!SystemUtils.isLinux()) {
             return;
@@ -128,12 +128,13 @@ public class WeatherInfoServiceImpl extends ServiceImpl<WeatherInfoMapper, Weath
         //获取所有合同段的定位信息
         List<ProjectContractArea> areaList = this.projectContractAreaClient.queryAllContractArea();
         Map<String, Map<String, String>> cachedWeatherMap = new HashMap<>();
+        String date = DateUtil.format(new Date(), "yyyy-MM-dd");
         for (ProjectContractArea area : areaList) {
             try {
                 //校验当前区域是否已经获取当天日期(手动补填或自动获取)
                 Map<String, Object> queryMap = new HashMap<>();
                 queryMap.put("contractAreaId", area.getId());
-                queryMap.put("recordTime", DateUtil.format(new Date(), "yyyy-MM-dd"));
+                queryMap.put("recordTime", date);
                 //查询
                 long count = this.count(Condition.getQueryWrapper(queryMap, WeatherInfo.class));
                 if (count > 0) {
@@ -146,30 +147,30 @@ public class WeatherInfoServiceImpl extends ServiceImpl<WeatherInfoMapper, Weath
                     String nums = stringRedisTemplate.opsForValue().get("blade-business::contractArea:yiKeYun:todayWeatherNums");
                     if (nums == null || Integer.parseInt(nums) <= 19000) {
                         // 重试三次
-                        weatherMap = YiKeYunApiUtils.getTodayWeatherByAdcodeTry(area.getCity_code() + "000000");
-                        if (weatherMap != null) {
+//                        weatherMap = YiKeYunApiUtils.getTodayWeatherByAdcodeTry(area.getCity_code() + "000000");
+                        Map<String, Map<String, String>> map = getHistoryWeather(area, date, date);
+                        if (map != null && !map.isEmpty()) {
+                            weatherMap = map.values().stream().findFirst().orElse( null);
+                        }
+                        if (weatherMap != null && !weatherMap.isEmpty()) {
                             cachedWeatherMap.put(area.getCity_code(), weatherMap);
                             if (weatherMap.get("nums") != null) {
                                 stringRedisTemplate.opsForValue().set("blade-business::contractArea:yiKeYun:todayWeatherNums", weatherMap.get("nums"), getSecondsUntilMidnight(), TimeUnit.SECONDS);
                             }
                         }
                     }
-                    //获取天气信息(百度天气)
-//                    if (weatherMap == null) {
-//                        weatherMap = BaiduApiUtil.getTodayWeather(area.getCity_code());
-//                    }
                 }
-                if (weatherMap != null) {
+                if (weatherMap != null && !weatherMap.isEmpty()) {
                     //计算平均气温
                     BigDecimal aver = (new BigDecimal(weatherMap.get("high")).add(new BigDecimal(weatherMap.get("low")))).divide(new BigDecimal("2"), 1, BigDecimal.ROUND_HALF_UP);
                     WeatherInfo newWeather = new WeatherInfo(String.valueOf(area.getId()), weatherMap.get("weather"), aver.toString(), weatherMap.get("high"), weatherMap.get("low"), weatherMap.get("windLevel"));
                     this.save(newWeather);
                     log.info("今日的天气已经同步完成!contractAreaId:" + area.getId() + ",syncTime:" + DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
                 } else {
-                    log.info("获取今日的天气失败!contractAreaId:" + area.getId() + ",syncTime:" + DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
+                    log.warn("获取今日的天气失败!contractAreaId:" + area.getId() + ",syncTime:" + DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
                 }
             } catch (Exception e) {
-                e.printStackTrace();
+                log.error("获取今日的天气失败!contractAreaId:" + area.getId() + ",syncTime:" + DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"), e);
             }
         }
         System.out.println("同步当天天气完成");
@@ -264,7 +265,7 @@ public class WeatherInfoServiceImpl extends ServiceImpl<WeatherInfoMapper, Weath
                         //判断集合中是否存在当前天气,如果不存在则获取
                         if (weatherMap == null || weatherMap.get(plainTime.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"))) == null) {
                             String month = plainTime.format(DateTimeFormatter.ofPattern("yyyyMM"));
-                            weatherMap = this.getHistoryWeather(projectContractArea, month);
+                            weatherMap = this.getHistoryWeather(projectContractArea, month, null);
                             if (weatherMap == null) {
                                 weatherMap = this.getWeather(county.toString(), month);
                                 if (weatherMap == null || weatherMap.get(plainTime.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"))) == null) {
@@ -361,7 +362,7 @@ public class WeatherInfoServiceImpl extends ServiceImpl<WeatherInfoMapper, Weath
             Map<String, List<LocalDate>> map = missDate.stream().collect(Collectors.groupingBy(l -> l.format(DateTimeFormatter.ofPattern("yyyyMM"))));
             for (String s : map.keySet()) {
                 //获取整月天气
-                weatherMap = this.getHistoryWeather(area, s);
+                weatherMap = this.getHistoryWeather(area, s, null);
                 if (weatherMap == null) {
                     weatherMap = this.getWeather(county.toString(), s);
                     if (weatherMap == null || weatherMap.size() == 0) {
@@ -426,7 +427,7 @@ public class WeatherInfoServiceImpl extends ServiceImpl<WeatherInfoMapper, Weath
         System.out.println("9999");
     }
 
-    public Map<String, Map<String, String>> getHistoryWeather(ProjectContractArea area, String month) {
+    public Map<String, Map<String, String>> getHistoryWeather(ProjectContractArea area, String month, String date) {
         String nums = stringRedisTemplate.opsForValue().get("blade-business::contractArea:yiKeYun:historyWeatherNums");
         if (nums != null && Integer.parseInt(nums) > 19000) {
             log.info("获取历史天气失败:易客云获取历史天气的api次数已用完。");
@@ -455,7 +456,7 @@ public class WeatherInfoServiceImpl extends ServiceImpl<WeatherInfoMapper, Weath
             cityId = jdbcTemplate.queryForObject("select id from m_yikeyun_weather_city where cityZh like '" + county + "%'", String.class);
         }
         if (cityId != null) {
-            Map<String, Map<String, String>> historyWeather = YiKeYunApiUtils.getHistoryWeather(cityId, Integer.parseInt(month.substring(0, 4)), Integer.parseInt(month.substring(4, 6)));
+            Map<String, Map<String, String>> historyWeather = YiKeYunApiUtils.getHistoryWeather(cityId, Integer.parseInt(month.substring(0, 4)), Integer.parseInt(month.substring(4, 6)), date);
             if (historyWeather != null) {
                 Map<String, String> map = historyWeather.get("nums");
                 if (map != null && map.get("nums") != null) {

+ 313 - 75
blade-service/blade-business/src/main/java/org/springblade/business/utils/FileUtils.java

@@ -8,6 +8,7 @@ import com.drew.metadata.exif.ExifIFD0Directory;
 import com.itextpdf.text.Document;
 import com.itextpdf.text.pdf.PdfCopy;
 import com.itextpdf.text.pdf.PdfReader;
+import net.coobird.thumbnailator.Thumbnails;
 import org.apache.commons.lang.StringUtils;
 import org.apache.poi.ss.usermodel.ClientAnchor;
 import org.apache.poi.ss.usermodel.Sheet;
@@ -24,6 +25,7 @@ import javax.imageio.IIOImage;
 import javax.imageio.ImageIO;
 import javax.imageio.ImageWriteParam;
 import javax.imageio.ImageWriter;
+import javax.imageio.stream.ImageOutputStream;
 import javax.servlet.http.HttpServletResponse;
 import java.awt.*;
 import java.awt.geom.AffineTransform;
@@ -118,92 +120,328 @@ public class FileUtils {
         }
     }
 
-    /**
-     * 图片缩放、压缩、旋转处理
-     *
-     * @param imageData
-     * @return
-     * @throws IOException
-     * @throws ImageProcessingException
-     * @throws MetadataException
-     */
-    public static byte[] compressImage(byte[] imageData) throws IOException, ImageProcessingException, MetadataException {
-        // 读取原始图像(处理旋转问题)
-        Metadata metadata = ImageMetadataReader.readMetadata(new ByteArrayInputStream(imageData));
-        if (metadata.containsDirectoryOfType(ExifIFD0Directory.class)) {
+//    /**
+//     * 图片缩放、压缩、旋转处理
+//     *
+//     * @param imageData
+//     * @return
+//     * @throws IOException
+//     * @throws ImageProcessingException
+//     * @throws MetadataException
+//     */
+//    public static byte[] compressImage(byte[] imageData) throws IOException, ImageProcessingException, MetadataException {
+//        // 读取原始图像(处理旋转问题)
+//        Metadata metadata = ImageMetadataReader.readMetadata(new ByteArrayInputStream(imageData));
+//        if (metadata.containsDirectoryOfType(ExifIFD0Directory.class)) {
+//            ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
+//            if (exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
+//                // 获取 Orientation 标签的值
+//                int orientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
+//                // 需要旋转图片
+//                BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(imageData));
+//                AffineTransform transform = new AffineTransform();
+//                if (orientation == 6) {
+//                    transform.rotate(Math.PI / 2, originalImage.getWidth() / 2, originalImage.getHeight() / 2);
+//                } else if (orientation == 8) {
+//                    transform.rotate(-Math.PI / 2, originalImage.getWidth() / 2, originalImage.getHeight() / 2);
+//                }
+//                AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
+//                originalImage = op.filter(originalImage, null);
+//                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+//                ImageIO.write(originalImage, "jpg", baos);
+//                imageData = baos.toByteArray();
+//            }
+//        }
+//        // 缩放图像
+//        String formatName = "JPEG";
+//        ByteArrayInputStream bais = new ByteArrayInputStream(imageData);
+//        BufferedImage originalImage = ImageIO.read(bais);
+//        long sizeLimit = 366912; //358KB
+//        int width = 768;
+//        int height = 1024;
+//        Image scaledImage = originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
+//        BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+//        resizedImage.getGraphics().drawImage(scaledImage, 0, 0, null);
+//
+//        // 压缩图像
+//        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+//        ImageIO.write(resizedImage, formatName, baos);
+//
+//        if (baos.size() <= sizeLimit) {
+//            // 图片大小已经小于等于目标大小,直接返回原始数据
+//            return baos.toByteArray();
+//        }
+//
+//        float quality = 0.9f; // 初始化压缩质量
+//        int retries = 10; // 最多尝试 10 次
+//
+//        while (baos.size() > sizeLimit && retries > 0) {
+//            // 压缩图像并重新计算压缩质量
+//            byte[] data = baos.toByteArray();
+//            bais = new ByteArrayInputStream(data);
+//            BufferedImage compressedImage = ImageIO.read(bais);
+//            baos.reset();
+//
+//            ImageWriter writer = null;
+//            Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatName);
+//            if (writers.hasNext()) {
+//                writer = writers.next();
+//            } else {
+//                throw new IllegalArgumentException("Unsupported image format: " + formatName);
+//            }
+//
+//            ImageWriteParam writeParam = writer.getDefaultWriteParam();
+//            writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+//            writeParam.setCompressionQuality(quality);
+//
+//            writer.setOutput(ImageIO.createImageOutputStream(baos));
+//            writer.write(null, new IIOImage(compressedImage, null, null), writeParam);
+//            writer.dispose();
+//
+//            float ratio = sizeLimit * 1.0f / baos.size();
+//            quality *= Math.sqrt(ratio);
+//            retries--;
+//        }
+//
+//        return baos.toByteArray();
+//    }
+
+    public static byte[] compressImage(byte[] imageData) throws IOException {
+        // 读取元数据判断是否需要旋转
+        int orientation = 1;
+        try {
+            Metadata metadata = ImageMetadataReader.readMetadata(new ByteArrayInputStream(imageData));
             ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
-            if (exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
-                // 获取 Orientation 标签的值
-                int orientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
-                // 需要旋转图片
-                BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(imageData));
-                AffineTransform transform = new AffineTransform();
-                if (orientation == 6) {
-                    transform.rotate(Math.PI / 2, originalImage.getWidth() / 2, originalImage.getHeight() / 2);
-                } else if (orientation == 8) {
-                    transform.rotate(-Math.PI / 2, originalImage.getWidth() / 2, originalImage.getHeight() / 2);
-                }
-                AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
-                originalImage = op.filter(originalImage, null);
-                ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                ImageIO.write(originalImage, "jpg", baos);
-                imageData = baos.toByteArray();
+            if (exifIFD0Directory != null && exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
+                orientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
             }
-        }
-        // 缩放图像
-        String formatName = "JPEG";
-        ByteArrayInputStream bais = new ByteArrayInputStream(imageData);
-        BufferedImage originalImage = ImageIO.read(bais);
-        long sizeLimit = 366912; //358KB
-        int width = 768;
-        int height = 1024;
-        Image scaledImage = originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
-        BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
-        resizedImage.getGraphics().drawImage(scaledImage, 0, 0, null);
-
-        // 压缩图像
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        ImageIO.write(resizedImage, formatName, baos);
-
-        if (baos.size() <= sizeLimit) {
-            // 图片大小已经小于等于目标大小,直接返回原始数据
-            return baos.toByteArray();
+        } catch (Exception e) {
+            // 元数据读取失败不影响主要流程
         }
 
-        float quality = 0.9f; // 初始化压缩质量
-        int retries = 10; // 最多尝试 10 次
-
-        while (baos.size() > sizeLimit && retries > 0) {
-            // 压缩图像并重新计算压缩质量
-            byte[] data = baos.toByteArray();
-            bais = new ByteArrayInputStream(data);
-            BufferedImage compressedImage = ImageIO.read(bais);
-            baos.reset();
-
-            ImageWriter writer = null;
-            Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatName);
-            if (writers.hasNext()) {
-                writer = writers.next();
-            } else {
-                throw new IllegalArgumentException("Unsupported image format: " + formatName);
+        // 使用Thumbnails处理旋转和压缩
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        long sizeLimit = 366912; // 358KB
+
+        try {
+            Thumbnails.of(new ByteArrayInputStream(imageData))
+                    .size(768, 1024)
+                    .outputFormat("jpg")
+                    .outputQuality(0.9)
+                    .useExifOrientation(true) // 自动处理EXIF旋转信息
+                    .toOutputStream(outputStream);
+
+            // 如果大小已满足要求,直接返回
+            if (outputStream.size() <= sizeLimit) {
+                return outputStream.toByteArray();
             }
 
-            ImageWriteParam writeParam = writer.getDefaultWriteParam();
-            writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
-            writeParam.setCompressionQuality(quality);
+            // 逐步降低质量直到满足大小要求
+            double quality = 0.8;
+            int attempts = 5;
 
-            writer.setOutput(ImageIO.createImageOutputStream(baos));
-            writer.write(null, new IIOImage(compressedImage, null, null), writeParam);
-            writer.dispose();
+            while (outputStream.size() > sizeLimit && attempts-- > 0) {
+                outputStream.reset();
+                Thumbnails.of(new ByteArrayInputStream(imageData))
+                        .size(768, 1024)
+                        .outputFormat("jpg")
+                        .outputQuality(quality)
+                        .useExifOrientation(true)
+                        .toOutputStream(outputStream);
 
-            float ratio = sizeLimit * 1.0f / baos.size();
-            quality *= Math.sqrt(ratio);
-            retries--;
-        }
+                quality *= 0.8; // 每次降低20%质量
+            }
 
-        return baos.toByteArray();
+            return outputStream.toByteArray();
+        } finally {
+            outputStream.close();
+        }
     }
 
+//    /**
+//     // * 图片缩放、压缩、旋转处理
+//     // *
+//     // * @param imageData
+//     // * @return
+//     // * @throws IOException
+//     // * @throws ImageProcessingException
+//     // * @throws MetadataException
+//     //     */
+//    public static byte[] compressImage(byte[] imageData) throws IOException {
+//        if (imageData == null || imageData.length == 0) {
+//            throw new IllegalArgumentException("图像数据不能为空");
+//        }
+//
+//        try {
+//            // 读取原始图像(处理旋转问题)
+//            Metadata metadata = ImageMetadataReader.readMetadata(new ByteArrayInputStream(imageData));
+//            if (metadata.containsDirectoryOfType(ExifIFD0Directory.class)) {
+//                ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
+//                if (exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
+//                    // 获取 Orientation 标签的值
+//                    int orientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
+//                    // 需要旋转图片
+//                    BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(imageData));
+//                    if (originalImage == null) {
+//                        throw new IOException("无法读取图像数据");
+//                    }
+//
+//                    AffineTransform transform = new AffineTransform();
+//                    if (orientation == 6) {
+//                        transform.rotate(Math.PI / 2, originalImage.getWidth() / 2, originalImage.getHeight() / 2);
+//                    } else if (orientation == 8) {
+//                        transform.rotate(-Math.PI / 2, originalImage.getWidth() / 2, originalImage.getHeight() / 2);
+//                    }
+//                    AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
+//                    originalImage = op.filter(originalImage, null);
+//                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+//                    ImageIO.write(originalImage, "jpg", baos);
+//                    imageData = baos.toByteArray();
+//                }
+//            }
+//        } catch (Exception e) {
+//            // 如果EXIF处理失败,继续处理原始图像数据
+//            System.err.println("处理图像EXIF信息时出错: " + e.getMessage());
+//        }
+//
+//        // 缩放图像
+//        String formatName = "JPEG";
+//        ByteArrayInputStream bais = new ByteArrayInputStream(imageData);
+//        BufferedImage originalImage = ImageIO.read(bais);
+//
+//        if (originalImage == null) {
+//            throw new IOException("无法读取图像数据");
+//        }
+//
+//        // 确保图像是兼容的格式
+//        BufferedImage compatibleImage = convertToSupportedFormat(originalImage);
+//
+//        long sizeLimit = 366912; //358KB
+//        int width = 768;
+//        int height = 1024;
+//
+//        // 使用更高质量的缩放方法
+//        Image scaledImage = originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
+//        BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+//        Graphics2D g2d = resizedImage.createGraphics();
+//        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+//        g2d.drawImage(scaledImage, 0, 0, null);
+//        g2d.dispose();
+//
+//        // 压缩图像
+//        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+//        try {
+//            boolean success = ImageIO.write(resizedImage, formatName, baos);
+//            if (!success) {
+//                throw new IOException("无法写入JPEG格式图像");
+//            }
+//        } catch (Exception e) {
+//            // 尝试使用ImageWriter直接写入
+//            writeImageWithDefaultSettings(resizedImage, baos, formatName);
+//        }
+//
+//        if (baos.size() <= sizeLimit) {
+//            // 图片大小已经小于等于目标大小,直接返回原始数据
+//            return baos.toByteArray();
+//        }
+//
+//        float quality = 0.9f; // 初始化压缩质量
+//        int retries = 10; // 最多尝试 10 次
+//
+//        while (baos.size() > sizeLimit && retries > 0) {
+//            // 压缩图像并重新计算压缩质量
+//            byte[] data = baos.toByteArray();
+//            bais = new ByteArrayInputStream(data);
+//            BufferedImage compressedImage = ImageIO.read(bais);
+//
+//            if (compressedImage == null) {
+//                throw new IOException("无法读取压缩后的图像数据");
+//            }
+//
+//            baos.reset();
+//
+//            ImageWriter writer = null;
+//            Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatName.toLowerCase());
+//            if (!writers.hasNext()) {
+//                // 尝试其他格式
+//                writers = ImageIO.getImageWritersByFormatName("jpeg");
+//            }
+//
+//            if (writers.hasNext()) {
+//                writer = writers.next();
+//            } else {
+//                throw new IllegalArgumentException("Unsupported image format: " + formatName);
+//            }
+//
+//            try {
+//                ImageWriteParam writeParam = writer.getDefaultWriteParam();
+//                if (writeParam.canWriteCompressed()) {
+//                    writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+//                    writeParam.setCompressionQuality(quality);
+//                }
+//
+//                try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) {
+//                    writer.setOutput(ios);
+//                    writer.write(null, new IIOImage(compressedImage, null, null), writeParam);
+//                }
+//            } finally {
+//                writer.dispose();
+//            }
+//
+//            float ratio = sizeLimit * 1.0f / baos.size();
+//            quality *= Math.sqrt(ratio);
+//            retries--;
+//        }
+//
+//        return baos.toByteArray();
+//    }
+//
+//    /**
+//     * 转换图像为支持的格式
+//     */
+//    private static BufferedImage convertToSupportedFormat(BufferedImage image) {
+//        if (image == null) return null;
+//
+//        // 检查是否为支持的类型
+//        int type = image.getType();
+//        if (type != BufferedImage.TYPE_INT_RGB && type != BufferedImage.TYPE_INT_ARGB) {
+//            // 转换为支持的格式
+//            BufferedImage converted = new BufferedImage(
+//                    image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
+//            Graphics2D g = converted.createGraphics();
+//            g.setColor(Color.WHITE);
+//            g.fillRect(0, 0, converted.getWidth(), converted.getHeight());
+//            g.drawImage(image, 0, 0, null);
+//            g.dispose();
+//            return converted;
+//        }
+//        return image;
+//    }
+//
+//    /**
+//     * 使用默认设置写入图像
+//     */
+//    private static void writeImageWithDefaultSettings(BufferedImage image, ByteArrayOutputStream baos, String formatName) throws IOException {
+//        Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatName.toLowerCase());
+//        if (!writers.hasNext()) {
+//            writers = ImageIO.getImageWritersByFormatName("jpeg");
+//        }
+//
+//        if (!writers.hasNext()) {
+//            throw new IOException("找不到JPEG图像写入器");
+//        }
+//
+//        ImageWriter writer = writers.next();
+//        ImageWriteParam param = writer.getDefaultWriteParam();
+//
+//        try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) {
+//            writer.setOutput(ios);
+//            writer.write(null, new IIOImage(image, null, null), param);
+//        } finally {
+//            writer.dispose();
+//        }
+//    }
+
     /**
      * 设置图片的定位、大小和合并单元格处理
      *

+ 7 - 598
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java

@@ -345,13 +345,10 @@ public class ExcelTabController extends BladeController {
         detail.setHtmlUrl(thmlUrl);
         excelTabService.saveOrUpdate(detail);
         // 解析html
-        expailHtmlInfo(thmlUrl, detail.getId(), detail.getTabType() + "");
+        excelTabService.expailHtmlInfo(thmlUrl, detail.getId(), detail.getTabType() + "");
         return R.success("上传成功");
     }
 
-    public void checkHtml(){
-
-    }
 
 
     public static InputStream getOSSInputStream(String urlStr) throws Exception {
@@ -807,6 +804,7 @@ public class ExcelTabController extends BladeController {
                 this.excelTabService.gsColor(pkeyId, process.getPKeyId().toString(), wbsTreeContract.getProjectId(), doc);
             }
             doc.select("Col").remove();
+
             org.springblade.manager.entity.TableInfo tableInfo = this.tableInfoService.getOne(Wrappers.<org.springblade.manager.entity.TableInfo>lambdaQuery().select(org.springblade.manager.entity.TableInfo::getId)
                     .eq(org.springblade.manager.entity.TableInfo::getTabEnName, wbsTreeContract.getInitTableName()).eq(org.springblade.manager.entity.TableInfo::getIsDeleted, 0).last("limit 1"));
             if (tableInfo != null ) {
@@ -824,6 +822,9 @@ public class ExcelTabController extends BladeController {
                     });
                 }
             }
+            // 电签显示文件
+           // List<TextdictInfoVO> textdictInfos = textdictInfoService.getTextdictListInfoByPkeyId(wbsTreeContract.getIsTypePrivatePid()+"",wbsTreeContract.getProjectId()+"");
+
             fileInputStream.close();
             return R.data(table + "");
         } catch (Exception e) {
@@ -999,598 +1000,6 @@ public class ExcelTabController extends BladeController {
         return doc.select("table").first() + "";
     }
 
-    // 上传解析 html
-    public void expailHtmlInfo(String thmlUrl, Long excelId, String tabType) throws Exception {
-
-        // 读取
-        File file1 = ResourceUtil.getFile(thmlUrl);
-        String htmlString = IoUtil.readToString(new FileInputStream(file1));
-
-        /*
-           解析
-           1 解析样式
-           2 计算坐标
-           3 计算区域位置
-         */
-        // 样式集合
-        Document doc = Jsoup.parse(htmlString);
-        // 解析 style
-        Map<String, String> styleMap = getHtmlStyle(doc);
-        //解析
-        Element table = doc.select("table").first();
-
-        Elements trs = table.select("tr");
-        Element tableinfo = trs.get(0).parent().parent().attr("style", "border-collapse: collapse;");
-
-        // 获取图片信息
-        Elements imgs = doc.select("img");
-
-
-        // 获取总行列数
-        int maxCol = doc.select("Col").size();
-        String[] rowData = new String[trs.size() + 5]; //本来加一的 害怕出现特殊情况 故意 加 5
-
-        // 行的状态
-        boolean index_state = false;
-        // 区域划分表示
-        int xy_type = 1;
-
-        // 解析 excel元素集合
-        List<ExctabCell> colTitle = new ArrayList<>();
-
-//      标题集合信息
-        List<Map<String, String>> zikey = new ArrayList<>();
-        for (int i = 0; i <= trs.size() - 1; i++) {
-            Element tr = trs.get(i);
-            Elements tds = tr.select("td");
-
-            String xyInof = getTrInfo(tds, styleMap, index_state, xy_type, maxCol, i, zikey);
-
-            xy_type = Integer.parseInt(xyInof.split(",")[0]);
-            tr.attr("xy_type", xyInof);
-            index_state = Boolean.parseBoolean(xyInof.split(",")[1]);
-            boolean isMaxCol = Integer.parseInt(xyInof.split(",")[2]) == maxCol;
-
-            boolean istrue = Boolean.parseBoolean(xyInof.split(",")[3]);
-
-
-            // 计算单元格坐标
-            int x = 0;
-
-            for (int j = 0; j < tds.size(); j++) {
-                {
-                    Element data = tds.get(j);
-                    Boolean isText = false;
-                    //判断高度
-                    String style = data.attr("style");
-                    if (StringUtils.isNotBlank(style)) {
-                        int index = style.lastIndexOf(":");
-                        String substring = style.substring(index + 1);
-                        String height = substring.substring(0, substring.length() - 3);
-                        int i1 = 0;
-                        try {
-                            i1 = Integer.parseInt(height);
-                        } catch (NumberFormatException e) {
-                            substring = style.substring(style.lastIndexOf(":", index - 1) + 1);
-                            height = substring.substring(0, substring.indexOf("px;"));
-                            i1 = Integer.parseInt(height);
-                        }
-                        if (i1 > 90) {
-                            isText = true;
-                        }
-                    }
-                    String trHtml = data.html();
-                    int colspan = data.attr("COLSPAN").equals("") ? 0 : Integer.parseInt(data.attr("COLSPAN"));
-                    int rowspan = data.attr("ROWSPAN").equals("") ? 0 : Integer.parseInt(data.attr("ROWSPAN"));
-                    String keyId = data.attr("class");
-                    if (StringUtils.isNotEmpty(keyId)) {
-                        String classInfo = styleMap.get(keyId);
-                        data.removeAttr("class");
-                    }
-
-                    Map<String, String> textObject = new HashMap<>();
-                    // 计算
-                    int x1 = 0;
-                    int x2 = 0;
-                    int y1 = 0;
-                    int y2 = 0;
-
-                    String textInfo = data.text().trim().replaceAll(" ", "");
-
-                    y1 = i + 1;
-                    //x 移位 算法
-                    String getRowInfo = rowData[y1];
-
-                    if (getRowInfo != null) {
-                        String[] dataInfo2 = getRowInfo.split(",");
-
-                        // 排序
-                        int lastMax = 0;
-                        List<String> datax = Arrays.stream(dataInfo2).sorted((a, b) -> Integer.parseInt(a.split(":")[1]) - Integer.parseInt(b.split(":")[1])).collect(Collectors.toList());
-                        List<String> lastdata = new ArrayList<>();
-                        //组合
-                        for (int h = 0; h < datax.size(); h++) {
-                            int mx1 = Integer.parseInt(datax.get(h).split(":")[0]);
-                            int mx2 = Integer.parseInt(datax.get(h).split(":")[1]);
-                            if (lastdata.size() == 0) {
-                                lastdata.add(datax.get(0));
-                            } else {
-                                if (lastMax + 1 == mx1) {
-                                    int minVal = Integer.parseInt(lastdata.get(lastdata.size() - 1).split(":")[0]);
-                                    lastdata.remove(lastdata.size() - 1);
-                                    lastdata.add(minVal + ":" + mx2);
-                                } else {
-                                    lastdata.add(datax.get(h));
-                                }
-                            }
-                            lastMax = mx2;
-                        }
-                        dataInfo2 = lastdata.stream().toArray(String[]::new);
-
-                        // 先逻辑处理 连续时,归一
-                        if ((dataInfo2[0].split(":")[0]).equals("1") && j == 0) {
-                            x = Integer.parseInt(dataInfo2[0].split(":")[1]);
-                        } else {
-                            for (int m = 0; m < dataInfo2.length; m++) {
-                                int mx1 = Integer.parseInt(dataInfo2[m].split(":")[0]);
-                                int mx2 = Integer.parseInt(dataInfo2[m].split(":")[1]);
-                                if ((mx1 - x) == 1 && mx1 > x) {
-                                    x = mx2;
-                                }
-                            }
-                        }
-                    }
-
-                    // X 坐标
-
-
-                    if (tds.size() == 1) {
-                        if (colspan == 0) {
-                            x1 = x + 1;
-                            x2 = x + 1;
-                        } else {
-                            if (x == 0) {
-                                x1 = 1;
-                                x2 = colspan;
-                            } else {
-                                x1 = x + 1;
-                                x2 = x + colspan;
-                            }
-                        }
-                    } else {
-                        if (colspan == 0) {
-                            x1 = x + 1;
-                            x2 = x + 1;
-                            x = x2;
-                        } else {
-                            x1 = x + 1;
-                            x2 = x + colspan;
-                            x = x2;
-                        }
-                    }
-
-
-                    //x y 坐标
-                    if (rowspan == 0) {
-                        y2 = i + 1;
-                    } else {
-                        y2 = i + rowspan;
-                        for (int k = 0; k < rowspan - 1; k++) {
-                            String dataInfo = rowData[k + 2 + i];
-                            if (dataInfo == null) {
-                                dataInfo = x1 + ":" + x2;
-                            } else {
-                                dataInfo = dataInfo + "," + x1 + ":" + x2;
-                                String[] arr = dataInfo.split(",");
-                                //排序
-                                for (int r = 0; r < arr.length - 1; r++) {
-                                    for (int q = arr.length - 2; q >= r; q--) {
-                                        Integer jval = Integer.parseInt(arr[q + 1].split(":")[0]);
-                                        Integer jval1 = Integer.parseInt(arr[q].split(":")[0]);
-                                        if (jval < jval1) {
-                                            String temp = arr[q + 1];
-                                            arr[q + 1] = arr[q];
-                                            arr[q] = temp;
-                                        }
-                                    }
-                                }
-
-                                //组合
-                                String newDataInfo = "";
-                                for (int r = 0; r < arr.length; r++) {
-                                    if (r == 0) {
-                                        newDataInfo = arr[0];
-                                    } else {
-                                        int StrMax = Integer.parseInt(newDataInfo.substring(newDataInfo.lastIndexOf(":") + 1, newDataInfo.length()));
-                                        Integer nowMin = Integer.parseInt(arr[r].split(":")[0]);
-                                        Integer nowMax = Integer.parseInt(arr[r].split(":")[1]);
-                                        if ((StrMax + 1) == nowMin) {
-                                            String lastStr = "";
-                                            if (newDataInfo.indexOf(",") >= 0) {
-                                                lastStr = newDataInfo.substring(newDataInfo.lastIndexOf(",") + 1, newDataInfo.length());
-                                                newDataInfo = newDataInfo.substring(0, newDataInfo.lastIndexOf(","));
-                                            } else {
-                                                lastStr = newDataInfo;
-                                                newDataInfo = "";
-                                            }
-                                            int lastmin = Integer.parseInt(lastStr.split(":")[0]);
-                                            if (StringUtils.isNotEmpty(newDataInfo)) {
-                                                newDataInfo = newDataInfo + "," + lastmin + ":" + nowMax;
-                                            } else {
-                                                newDataInfo = lastmin + ":" + nowMax;
-                                            }
-                                        } else {
-                                            newDataInfo = newDataInfo + "," + nowMin + ":" + nowMax;
-                                        }
-                                    }
-                                }
-                                dataInfo = newDataInfo;
-                            }
-                            rowData[k + 2 + i] = dataInfo;
-                        }
-                    }
-
-                    data.text(textInfo.replaceAll(" ", ""));
-                    if (textInfo.indexOf("□") < 0 && !textInfo.isEmpty() && !(textInfo.equals("/") && textInfo.length() < 2) && !(textInfo.indexOf("年") >= 0 && textInfo.indexOf("月") >= 0 && textInfo.indexOf("日") >= 0) && !textInfo.equals("—") && !textInfo.equals("-")) {  // 标题区域
-                        Map<String, String> dataInfo = new HashMap<String, String>();
-                        dataInfo.put("name", textInfo);
-                        dataInfo.put("x1", x1 + "");
-                        dataInfo.put("x2", x2 + "");
-                        dataInfo.put("y1", y1 + "");
-                        dataInfo.put("y2", y2 + "");
-                        dataInfo.put("xytype", xy_type + "");
-                        if (textInfo.indexOf("/") < 0 || (textInfo.indexOf("/") >= 0 && textInfo.length() > 1)) { // 带/为分割数据
-                            zikey.add(dataInfo);
-                        }
-                    } else { //空行
-                        List<Map<String, String>> left = new ArrayList<>();
-                        List<Map<String, String>> top = new ArrayList<>();
-                        for (int k = 0; k < zikey.size(); k++) {
-                            String name = zikey.get(k).get("name");
-                            int xx1 = Integer.parseInt(zikey.get(k).get("x1"));
-                            int xx2 = Integer.parseInt(zikey.get(k).get("x2"));
-                            int yy1 = Integer.parseInt(zikey.get(k).get("y1"));
-                            int yy2 = Integer.parseInt(zikey.get(k).get("y2"));
-                            int xytype2 = Integer.parseInt(zikey.get(k).get("xytype"));
-
-                            // 左匹配
-                            if (yy1 <= y1 && yy2 >= y2 && xx2 < x1 && xytype2 == xy_type) {
-                                left.add(zikey.get(k));
-                            }
-
-                            //向 上 匹配
-                            if (index_state) {
-                                if (xx1 <= x1 && xx2 >= x2 && yy2 < y1 && xytype2 == xy_type) {
-                                    top.add(zikey.get(k));
-                                }
-                            }
-                        }
-
-                        String inputText = "";
-                        // 特征值赛选 规则
-                        for (int k = 0; k < left.size(); k++) { // 左计算
-                            String name = left.get(k).get("name");
-                            int xx1 = Integer.parseInt(left.get(k).get("x1"));
-                            int xx2 = Integer.parseInt(left.get(k).get("x2"));
-                            int yy1 = Integer.parseInt(left.get(k).get("y1"));
-                            int yy2 = Integer.parseInt(left.get(k).get("y2"));
-
-                            if (!StringUtil.isNumeric(name) && name.length() <= 20) { // 数字不匹配
-                                if (index_state) { // 正向规则匹配
-                                    if (istrue) { // 是否空格等于值
-                                        if (x1 - xx2 <= 1 && y1 == yy2) {
-                                            inputText = name;
-                                        } else {
-                                            inputText += name + "_";
-                                        }
-                                    } else {
-                                        inputText += name + "_";
-                                    }
-                                } else {
-                                    if (x1 - xx2 <= 1 && y1 == yy2) {
-                                        inputText = name;
-                                    }
-                                }
-                            }
-                        }
-
-                        // 特征值赛选 规则
-                        if (top != null && top.size() >= 1) {
-                            for (int k = 0; k < top.size(); k++) { // 向上计算
-                                String name = top.get(k).get("name");
-                                int xx1 = Integer.parseInt(top.get(k).get("x1"));
-                                int xx2 = Integer.parseInt(top.get(k).get("x2"));
-                                int yy1 = Integer.parseInt(top.get(k).get("y1"));
-                                int yy2 = Integer.parseInt(top.get(k).get("y2"));
-                                if (!StringUtil.isNumeric(name) && name.length() <= 20) {
-                                    inputText += name + "_";
-                                }
-                            }
-                        }
-
-                        if (inputText != null && inputText != "" && inputText.indexOf("_") >= 0) {
-                            inputText = inputText.substring(0, inputText.lastIndexOf("_"));
-                        }
-
-                        // 质检表特殊处理匹配
-
-                        String parm = i + "," + j + "," + x1 + "," + x2 + "," + y1 + "," + y2 + ",$event";
-                        // 设置文本信息
-                        ExctabCell exctabCell = new ExctabCell();
-                        if ((textInfo.indexOf("年") >= 0 && textInfo.indexOf("月") >= 0 && textInfo.indexOf("日") >= 0) || inputText.indexOf("日期") >= 0) {
-                            if (inputText.indexOf("日期") >= 0) {
-                                data.empty().append("<el-date-picker type='date' @keyDowns='dateKeydown()' format='YYYY年MM月DD日' value-format='YYYY年MM月DD日' @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;' placeholder='" + inputText + "'> </el-date-picker>");
-                            } else if (textInfo.indexOf("年") >= 0 && textInfo.indexOf("月") >= 0 && textInfo.indexOf("日") >= 0) {
-                                if (inputText.indexOf("专业监理工程师") >= 0) {
-                                    inputText = "专业监理工程师_年月日";
-                                } else if (inputText.indexOf("质检工程师") >= 0) {
-                                    inputText = "质检工程师_年月日";
-                                } else {
-                                    inputText = "年月日";
-                                }
-                            }
-                            data.empty().append("<el-date-picker @keyDowns='dateKeydown()'  type='date' format='YYYY年MM月DD日' value-format='YYYY年MM月DD日' @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;' placeholder='年月日'> </el-date-picker>");
-                            exctabCell.setTextInfo(inputText);
-                            exctabCell.setExctabId(excelId);
-                            exctabCell.setIsDeleted(0);
-                            exctabCell.setXys(i + "_" + j);
-                            colTitle.add(exctabCell);
-                            data.attr("title", inputText);
-
-                        } else if (textInfo.indexOf("□") >= 0) { //多选框
-                            exctabCell.setTextInfo(inputText);
-                            exctabCell.setExctabId(excelId);
-                            exctabCell.setIsDeleted(0);
-                            exctabCell.setXys(i + "_" + j);
-                            colTitle.add(exctabCell);
-                            data.attr("title", inputText);
-                            // 添加多选框
-
-                            String[] cheText = textInfo.split("□");
-                            JSONArray objs = new JSONArray();
-                            if (cheText != null && cheText.length >= 1) {
-                                int key = 1;
-                                for (String keyval : cheText) {
-                                    JSONObject jsonObject = new JSONObject();
-                                    if (StringUtils.isNotEmpty(keyval)) {
-                                        jsonObject.put("key", key);
-                                        jsonObject.put("name", keyval);
-                                        objs.add(jsonObject);
-                                        keyId += 1;
-                                    }
-                                }
-                            } else {
-                                JSONObject jsonObject = new JSONObject();
-                                jsonObject.put("key", "1");
-                                jsonObject.put("name", "");
-                                objs.add(jsonObject);
-                            }
-
-                            String checkbox = "<hc-form-checkbox-group @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft' @keydown.shift.right='keyupShiftRight' :objs='" + objs + "'  @change='checkboxGroupChange' @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " placeholder=''> </hc-form-checkbox-group>";
-                            data.empty().append(checkbox);
-
-                        } else {
-
-                            if (index_state) { // 区域内
-                                if (rowspan >= 1 || isText) {
-                                    data.empty().append("<el-input type='textarea' @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft'  @keydown.shift.right='keyupShiftRight'  @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;'   :rows=" + rowspan * 2 + " placeholder=''> </el-input>");
-                                } else {
-                                    data.empty().append("<el-input type='text' @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft'  @keydown.shift.right='keyupShiftRight'  @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;' placeholder=''> </el-input>");
-                                }
-                            } else if (tabType.equals("100") && i < 2) { //计量特殊添加
-                                data.empty().append("<el-input type='text' @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft'  @keydown.shift.right='keyupShiftRight'  @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;' placeholder=''> </el-input>");
-                            } else { // 区域外
-                                if (j == 0) {
-                                    if (colspan == maxCol && i >= 1) {
-                                        if (rowspan >= 1 || isText) {
-                                            data.empty().append("<el-input @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft' @keydown.shift.right='keyupShiftRight' type='textarea'  @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;'   :rows=" + rowspan * 2 + " placeholder=''> </el-input>");
-                                        } else {
-                                            data.empty().append("<el-input @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft' @keydown.shift.right='keyupShiftRight' type='text' @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;' placeholder=''> </el-input>");
-                                        }
-                                    }
-                                } else {
-                                    Element bforData = tds.get(j - 1);
-                                    if (!bforData.text().isEmpty() || bforData.html().indexOf("hc-form-checkbox-group") >= 0) {
-                                        if (rowspan >= 1 || isText) {
-                                            data.empty().append("<el-input @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft' @keydown.shift.right='keyupShiftRight' type='textarea' @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;'   :rows=" + rowspan * 2 + " placeholder=''> </el-input>");
-                                        } else {
-                                            data.empty().append("<el-input @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft'  @keydown.shift.right='keyupShiftRight' type='text' @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;' placeholder=''> </el-input>");
-                                        }
-                                    }
-                                }
-                            }
-
-                            if (!inputText.equals("")) {
-                                exctabCell.setExctabId(excelId);
-                                exctabCell.setTextInfo(inputText);
-                                if (inputText.contains("日期") || inputText.contains("年") || inputText.contains("月") || inputText.contains("日")) {
-                                    //日期
-                                    exctabCell.setTextElementType(4);
-                                } else if (inputText.indexOf("签字") >= 0) {
-                                    exctabCell.setTextElementType(6);
-                                } else {
-                                    //字符串
-                                    exctabCell.setTextElementType(1);
-                                }
-
-                                exctabCell.setIsDeleted(0);
-                                exctabCell.setXys(i + "_" + j);
-                                colTitle.add(exctabCell);
-                            }
-                            data.attr("title", inputText);
-                        }
-                    }
-                }
-            }
-        }
-        //System.out.println(zikey);
-        // 去掉重复的数
-        Map<String, String> groupMap2 = colTitle.stream()
-                .collect(Collectors.groupingBy(ExctabCell::getTextInfo, Collectors.mapping(ExctabCell::getXys, Collectors.joining(","))));
-
-        exctabCellService.DeletExcelByTableId(excelId + "");
-
-        List<ExctabCell> colTitle2 = new ArrayList<>();
-        for (String title : groupMap2.keySet()) {
-            ExctabCell exctabCell = new ExctabCell();
-            exctabCell.setExctabId(excelId);
-            exctabCell.setIsDeleted(0);
-            exctabCell.setTextInfo(title);
-            exctabCell.setCreateTime(new Date());
-
-            if (title.contains("日期") || title.contains("年") || title.contains("月") || title.contains("日")) {
-                //日期
-                exctabCell.setTextElementType(4);
-            } else {
-                //字符串
-                exctabCell.setTextElementType(1);
-            }
-
-            exctabCell.setXys(groupMap2.get(title));
-            colTitle2.add(exctabCell);
-        }
-        exctabCellService.saveBatch(colTitle2);
-
-
-        //对excel 的图片进行操作
-        ExcelTab exceltab = excelTabService.getById(excelId);
-        if (exceltab != null) {
-            // 获取excle 的数据
-            String fileUrl = exceltab.getFileUrl();
-            InputStream ossInputStream = CommonUtil.getOSSInputStream(fileUrl);
-            Workbook wb = new Workbook();
-            wb.loadFromMHtml(ossInputStream);
-            Worksheet sheet = wb.getWorksheets().get(0);
-            PicturesCollection pictures = sheet.getPictures();
-            if (pictures != null && pictures.size() >= 1) {
-                for (int i = 0; i < pictures.size(); i++) {
-                    ExcelPicture pic = pictures.get(i);
-                    int x = pic.getLeftColumn();
-                    int y = pic.getBottomRow();
-                    Elements select = doc.select("el-input[x1=" + x + "][y1=" + y + "]");
-                    System.out.println("xx=--" + x);
-                    System.out.println("yy=--" + y);
-                    if (select != null && select.size() >= 1) {
-                        Element element = select.get(0);
-                        Element elementP = element.parent();
-                        element.remove();
-                        Element imgele = imgs.get(i);
-                        imgele.removeAttr("class");
-                        elementP.append(imgele.toString());
-                    }
-                }
-            }
-
-            ossInputStream.close();
-        }
-        // 移除图片
-        imgs.remove();
-        // 保存
-        exceltab.setIsDeleted(8);
-        excelTabService.saveOrUpdate(exceltab);
-        File writefile = new File(thmlUrl);
-        FileUtil.writeToFile(writefile, doc.html(), Boolean.parseBoolean("UTF-8"));
-    }
-
-    //计算区域坐标
-    public static String getTrInfo(Elements tds, Map<String, String> styleMap, boolean index_state, Integer xy_type, int maxCol, int y, List<Map<String, String>> zikey) {
-
-        int x_width = 0;
-        int y_width = 0;
-        int text_width = 0;
-        int width = 0;
-
-        int null_count = 0;
-        int val_count = 0;
-
-        boolean istrue = true;
-
-        // 上 tr 长度
-
-        List<Map<String, String>> maxList = zikey.stream().filter(map -> Integer.parseInt(map.get("y1")) <= y && y <= Integer.parseInt(map.get("y2"))).collect(Collectors.toList());
-
-        int top1_max = 0;
-        if (maxList != null && maxList.size() >= 1) {
-            top1_max = maxList.stream().mapToInt(m -> Integer.parseInt(m.get("x2"))).max().getAsInt();
-        }
-
-
-        //区域计算
-        for (int j = 0; j < tds.size(); j++) {
-            Element data = tds.get(j);
-            String keyId = data.attr("class");
-            int colspan = data.attr("COLSPAN").equals("") ? 1 : Integer.parseInt(data.attr("COLSPAN"));
-            if (colspan == 0) {
-                colspan = data.attr("colspan").equals("") ? 1 : Integer.parseInt(data.attr("colspan"));
-            }
-            String classInfo = styleMap.get(keyId);
-            String textInfo = data.text().trim().replaceAll(" ", "");
-
-            if (classInfo == null) {
-                classInfo = data.attr("style");
-            } else {
-                data.removeAttr("class");
-                data.attr("style", styleMap.get(keyId).replaceAll("break-word", "inherit"));
-            }
-
-            // 计算线开始
-            if (classInfo.indexOf("border-left-style") >= 0 && classInfo.indexOf("border-top-style") >= 0 && classInfo.indexOf("border-right-style") >= 0) {
-                x_width += colspan;
-            }
-
-            // 计算结束
-            if (classInfo.indexOf("border-left-style") < 0 && (classInfo.indexOf("border-top-style") < 0 || classInfo.indexOf("border-top-style") >= 0) && classInfo.indexOf("border-bottom-style") < 0 && classInfo.indexOf("border-right-style") < 0) {
-                y_width += colspan;
-            }
-
-            String name = data.text();
-            if (!name.isEmpty()) {
-                text_width += colspan;
-            }
-            width += colspan;
-
-            if (!textInfo.isEmpty() && !(textInfo.equals("/") && textInfo.length() <= 2) && !(textInfo.indexOf("年") >= 0 && textInfo.indexOf("月") >= 0 && textInfo.indexOf("日") >= 0) && !textInfo.equals("—") && !textInfo.equals("-")) {  // 标题区域
-                val_count++;
-            } else {
-                null_count++;
-            }
-        }
-
-        // 在区域内
-        //  System.out.println(index_state+"——"+y+"__"+x_width);
-
-        if (index_state) {
-            //是否需要改变
-            if (maxCol == y_width) { // 是否结束区域值
-                index_state = false;
-                xy_type += 1;
-            }
-            if (maxCol == text_width && top1_max != text_width) { // 是否区域开始时
-                xy_type += 1;
-            }
-        } else { // 区域外
-            if (maxCol == x_width) {
-                index_state = true;
-                xy_type += 1;
-            }
-        }
-
-        // 空是否等于值的个数
-        istrue = null_count == val_count && width == maxCol;
-        return xy_type + "," + index_state + "," + width + "," + istrue;
-    }
-
-    // 获取解析样式
-    public static Map<String, String> getHtmlStyle(Document doc) {
-        Map<String, String> styleMap = new HashMap<>();
-        Element style = doc.select("style").first();
-        Matcher cssMatcher = Pattern.compile("(\\w+)\\s*[{]([^}]+)[}]").matcher(style.html());
-        while (cssMatcher.find()) {
-            styleMap.put(cssMatcher.group(1), cssMatcher.group(2));
-        }
-        return styleMap;
-    }
-
     /**
      * 清表生成html
      */
@@ -2061,7 +1470,7 @@ public class ExcelTabController extends BladeController {
         }
         executionTime.info("----数据合并前----");
         List<String> errorPKeyIds = new ArrayList<>();
-        //单个pdf加载
+        //单个pdf加载F
         tableInfoList = (List<TableInfo>) result.getData();
         if (tableInfoList != null) {
             String finalSingnType = singnType;
@@ -3827,7 +3236,7 @@ public class ExcelTabController extends BladeController {
             Thread.sleep(100);
             sheet.saveToHtml(html_url, options);
             Thread.sleep(100);
-            this.expailHtmlInfo(html_url, p_key_id, detail.getTabType() + "");
+            excelTabService.expailHtmlInfo(html_url, p_key_id, detail.getTabType() + "");
             Thread.sleep(100);
             EexpaileInfo(html_url, p_key_id);
             System.out.println(i);

+ 15 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreePrivateController.java

@@ -1016,4 +1016,19 @@ public class WbsTreePrivateController extends BladeController {
             .collect(Collectors.joining(","));
     }
 
+    /**
+     * wbs私有树懒加载获取项目私有节点树形结构--(表单类型划分树)
+     */
+    @GetMapping("/tab-Type-lazy-tree-project")
+    @ApiOperationSupport(order = 40)
+    @ApiOperation(value = "项目级懒加载节点树形结构-表单类型划分树", notes = "传入项目Id和父Id")
+    @ApiImplicitParams(value = {
+            @ApiImplicitParam(name = "parentId", value = "父级id", required = true),
+            @ApiImplicitParam(name = "projectId", value = "项目id", required = true),
+            @ApiImplicitParam(name = "titleName", value = "搜索关键字", required = true)
+    })
+    public R<IPage<TreeNodeVOByTabType>> tabTypeLazyTreeByProject(Long parentId, String projectId, String titleName, Query query) {
+        IPage<TreeNodeVOByTabType> page = wbsTreePrivateService.tabTypeLazyTreeByProject(Condition.getPage(query), parentId, projectId, titleName);
+        return R.data(page);
+    }
 }

+ 1 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/feign/ExcelTabClientImpl.java

@@ -498,7 +498,7 @@ public class ExcelTabClientImpl implements ExcelTabClient {
 
     @Override
     public void expailHtmlInfo(String thmlUrl, Long id, String s) throws Exception {
-        excelTabController.expailHtmlInfo(thmlUrl, id, s);
+        excelTabService.expailHtmlInfo(thmlUrl, id, s);
     }
 
     @Override

+ 122 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/FormulaZhiZuo.java

@@ -16,6 +16,8 @@ import org.springblade.manager.utils.RandomNumberHolder;
 import org.springframework.stereotype.Component;
 
 import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 
 @Component
@@ -116,6 +118,126 @@ public class FormulaZhiZuo implements FormulaStrategy {
         }
         System.out.println(values);
     }
+     //优化之后的代码 放开注释可直接使用
+//    @Override
+//    public void execute(FormData cur, TableElementConverter tec) {
+//        // 1. 提取关键信息,增加空指针防护
+//        String relyStr = cur.getFormula().getRely();
+//        if (relyStr == null || !relyStr.contains(":")) {
+//            throw new IllegalArgumentException("无效的依赖格式: " + relyStr);
+//        }
+//        String key = relyStr.substring(relyStr.indexOf(':') + 1);
+//
+//        // 2. 获取随机数映射并检查非空
+//        HashMap<Long, String> randomNumberMap = RandomNumberHolder.getRandomNumber();
+//        if (randomNumberMap == null || randomNumberMap.isEmpty()) {
+//            throw new IllegalStateException("随机数映射为空,无法继续处理");
+//        }
+//        Map.Entry<Long, String> firstRandomEntry = randomNumberMap.entrySet().iterator().next();
+//
+//        // 3. 查找对应的表格信息
+//        Optional<TableInfo> tableInfoOpt = tec.getTableInfoList().stream()
+//                .filter(tableInfo -> firstRandomEntry.getKey().toString().equals(tableInfo.getPkeyId()))
+//                .findFirst();
+//        if (!tableInfoOpt.isPresent()) {
+//            throw new IllegalStateException("未找到对应的表格信息,pkeyId: " + firstRandomEntry.getKey());
+//        }
+//        TableInfo targetTableInfo = tableInfoOpt.get();
+//
+//        // 4. 解析数据字符串(预编译正则提升性能)
+//        String dataStr = targetTableInfo.getDataMap().get(key);
+//        if (dataStr == null) {
+//            throw new IllegalArgumentException("表格数据中未找到key: " + key);
+//        }
+//        String[] dataSegments = dataStr.split("☆");
+//        Pattern pattern = Pattern.compile("_\\^_(.*?)_"); // 预编译正则表达式
+//        Map<Integer, Double> keyToValueMap = new HashMap<>();
+//
+//        for (String segment : dataSegments) {
+//            Matcher matcher = pattern.matcher(segment);
+//            if (matcher.find()) {
+//                try {
+//                    Integer mapKey = Integer.parseInt(matcher.group(1));
+//                    int splitIndex = segment.indexOf("_^_");
+//                    if (splitIndex > 0) {
+//                        Double value = Double.valueOf(segment.substring(0, splitIndex));
+//                        keyToValueMap.put(mapKey, value);
+//                    }
+//                } catch (NumberFormatException e) {
+//                    // 记录日志而非中断,提高容错性
+//                    System.err.println("解析数据失败,segment: " + segment + ", 错误: " + e.getMessage());
+//                }
+//            }
+//        }
+//
+//        // 5. 准备目标值列表(清空原有值)
+//        List<ElementData> elementDataList = cur.getValues();
+//        elementDataList.forEach(e -> e.setValue(null));
+//
+//        // 6. 排序并创建Y值到ElementData的映射(优化查找效率)
+//        TreeMap<Integer, Double> sortedDataMap = new TreeMap<>(keyToValueMap);
+//        Map<Integer, ElementData> yToElementMap = new HashMap<>();
+//        for (ElementData data : elementDataList) {
+//            yToElementMap.put(data.getY(), data);
+//        }
+//
+//        // 7. 处理数据(提取重复逻辑为方法)
+//        if (!sortedDataMap.isEmpty()) {
+//            Map.Entry<Integer, Double> firstEntry = sortedDataMap.firstEntry();
+//            processDataRange(sortedDataMap, yToElementMap, firstEntry, 0, 4, 1);
+//            processDataRange(sortedDataMap, yToElementMap, firstEntry, 5, 9, 5);
+//            processDataRange(sortedDataMap, yToElementMap, firstEntry, 10, Integer.MAX_VALUE, 10);
+//        }
+//
+//        System.out.println(elementDataList);
+//    }
+//
+//    /**
+//     * 处理指定范围的数据
+//     * @param sortedDataMap 排序后的数据映射
+//     * @param yToElementMap Y值到ElementData的映射
+//     * @param firstEntry 第一个数据条目
+//     * @param startIndex 起始索引
+//     * @param endIndex 结束索引
+//     * @param offset 偏移量
+//     */
+//    private void processDataRange(TreeMap<Integer, Double> sortedDataMap,
+//                                  Map<Integer, ElementData> yToElementMap,
+//                                  Map.Entry<Integer, Double> firstEntry,
+//                                  int startIndex,
+//                                  int endIndex,
+//                                  int offset) {
+//        if (sortedDataMap.size() < startIndex + 1) {
+//            return; // 数据量不足,直接返回
+//        }
+//
+//        Integer baseKey = firstEntry.getKey() + offset;
+//        Double baseValue = sortedDataMap.get(baseKey);
+//        if (baseValue == null) {
+//            return;
+//        }
+//
+//        for (int i = startIndex; i <= endIndex; i++) {
+//            if (i >= sortedDataMap.size()) {
+//                break;
+//            }
+//
+//            Integer currentKey = baseKey + (i - offset);
+//            Double currentValue = sortedDataMap.get(currentKey);
+//            if (currentValue == null) {
+//                continue;
+//            }
+//
+//            // 计算绝对值差值
+//            Double diffValue = Math.abs(baseValue - currentValue);
+//
+//            // 查找并设置值(O(1)复杂度)
+//            ElementData targetData = yToElementMap.get(currentKey);
+//            if (targetData != null) {
+//                targetData.setValue(diffValue);
+//            }
+//        }
+//    }
 
     @Override
     public boolean accept(FormData fd) {

+ 1 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/SaveUserInfoByProjectMapper.xml

@@ -73,7 +73,7 @@
     <select id="queryCurrentUserDownAllContractAndProjectId" resultMap="contractInfoResultMap">
         select project_id, contract_id
         from m_project_assignment_user
-        where is_deleted = 0
+        where is_deleted = 0 and project_id is not null and contract_id is not null
           and user_id = #{userId}
     </select>
 

+ 4 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/TextdictInfoMapper.java

@@ -46,8 +46,12 @@ public interface TextdictInfoMapper extends EasyBaseMapper<TextdictInfo> {
 
 
     List<TextdictInfoVO> selectTextdictInfoByExcelId(IPage page, TextdictInfoVO textdictInfo);
+
     List<TextdictInfoVO> selectTextdictBYIds(@Param("ids") List<String> ids,@Param("projectId") String projectId);
 
     TextdictInfo selectTextdictInfoOne(@Param("id") String id,@Param("sigRoleId") String sigRoleId,@Param("projectId") String projectId);
 
+    //通过表单Id 获取电签信息
+    List<TextdictInfoVO> getTextdictListInfoByPkeyId(@Param("tabId") String tabId,@Param("projectId") String projectId);
+
 }

+ 4 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/TextdictInfoMapper.xml

@@ -65,4 +65,8 @@
             and sig_role_id = #{sigRoleId}
             and project_id= #{projectId}
     </select>
+
+    <select id="getTextdictListInfoByPkeyId" resultMap="textdictInfoVoResultMap">
+        SELECT a.* from m_textdict_info a where 1=0
+    </select>
 </mapper>

+ 2 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreePrivateMapper.java

@@ -43,6 +43,8 @@ public interface WbsTreePrivateMapper extends EasyBaseMapper<WbsTreePrivate> {
 
     List<TreeNodeVOByTabType> tabTypeLazyTree(IPage page, Long parentId, String projectId, String titleName);
 
+    List<TreeNodeVOByTabType> tabTypeLazyTreeByProject(IPage page, Long parentId, String projectId, String titleName);
+
     List<TreeNodeVOByTabType> tabTypeLazyTreeAll(Long parentId, String titleName);
 
     int updateByPKeyId(@Param("pKeyId") Long pKeyId, @Param("wbsTP") WbsTreePrivate wbsTP);

+ 56 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreePrivateMapper.xml

@@ -97,12 +97,18 @@
         <result column="title" property="title"/>
         <result column="has_children" property="hasChildren"/>
         <result column="primaryKeyId" property="primaryKeyId"/>
+        <result column="pkeyId" property="pkeyId"/>
         <result column="tabOwner" property="tabOwner"/>
         <result column="tabType" property="tabType"/>
         <result column="elementTotal" property="elementTotal"/>
         <result column="fillRate" property="fillRate"/>
         <result column="initTableId" property="initTableId"/>
+        <result column="initTableName" property="initTableName"/>
         <result column="excelIds" property="excelIds"/>
+        <result column="excelId" property="excelId"/>
+        <result column="table_type" property="tableType"/>
+        <result column="table_owner" property="tableOwner"/>
+        <result column="is_link_table" property="isLinkTable"/>
     </resultMap>
 
 
@@ -875,7 +881,7 @@
         delete
         from m_wbs_tree_private
         WHERE p_key_id = #{pKeyId}
-          and project_id = #{projectId}AND type = 10
+          and project_id = #{projectId} AND type in(2, 10)
     </delete>
 
     <delete id="delTableById">
@@ -958,4 +964,53 @@
         from u_contract_log ucl
         where contract_id = #{contractId} and is_deleted = 0 and pdf_url IS NOT NULL order by record_time
     </select>
+
+
+    <!-- 项目级 表单类型分类 wbs树 -->
+    <select id="tabTypeLazyTreeByProject" resultMap="treeNodeResultMapTabType">
+        SELECT a.exceIds AS excelIds,initTableName,a.exceIds as excelId,table_type, table_owner,is_link_table,
+        p_key_id as id,p_key_id as primaryKeyId,title,parent_id,fill_rate as fillRate,initTableId,p_key_id as pkeyId,
+        (SELECT dict_value from blade_dict where code='table_type' and dict_key not in(-1,0) and dict_key=table_type )
+        as tabType,
+        (SELECT count(1) FROM m_wbs_form_element WHERE f_id = initTableId and is_deleted=0) AS "elementTotal",
+        (SELECT dict_value from blade_dict where code='owner_type' and dict_key not in(-1,0) and dict_key=table_owner )
+        as tabOwner,
+        /*排序*/
+        -- (SELECT sort FROM m_wbs_node_sort c WHERE a.p_key_id = c.p_key_id) AS sort,
+        (
+        SELECT
+        CASE WHEN count(1) > 0 THEN 1 ELSE 0 END
+        FROM
+        (
+        SELECT '12345678910' as p_key_id , '表单类型' as node_name, 0 as parent_id
+        union all
+        SELECT dict_key as p_key_id ,dict_value as node_name,'12345678910' as parent_id from blade_dict where
+        code='table_type' and dict_key not in(-1,0)
+        union all
+        SELECT p_key_id,node_name,table_type as parent_id from m_wbs_tree_private WHERE project_id=#{projectId} and
+        is_deleted=0 and type in(2,10) and table_type is not NULL GROUP BY node_name
+        ) b
+        WHERE
+        b.parent_id = a.p_key_id
+        ) AS "has_children"
+        from (
+        SELECT '12345678910' as p_key_id , '表单类型' as title, 0 as parent_id,0 as table_type,0 as fill_rate,0 as
+        table_owner,0 as initTableId,0 as exceIds,0 as initTableName,0 as is_link_table
+        union all
+        SELECT dict_key as p_key_id ,dict_value as title,'12345678910' as parent_id,0 as table_type,0 as fill_rate,0 as
+        table_owner,0 as initTableId,0 as exceIsd,0 as initTableName,0 as is_link_table from blade_dict where code='table_type' and dict_key not in(-1,0)
+        union all
+        SELECT p_key_id,node_name as title,table_type as parent_id,table_type,fill_rate,table_owner,init_table_id as initTableId,excel_id AS excelIds,init_table_name as  initTableName,is_link_table
+        from m_wbs_tree_private WHERE project_id=#{projectId} and type in(2,10) and
+        is_deleted=0 and table_type is not NULL GROUP BY node_name
+        ) a where 1=1
+        <if test="parentId != null and parentId != ''">
+            and a.parent_id = #{parentId}
+        </if>
+        <if test="titleName != null and titleName != ''">
+            and a.title like concat('%',#{titleName},'%') and LENGTH(a.p_key_id)>11
+        </if>
+        /*排序*/
+        -- order by sort,title
+    </select>
 </mapper>

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

@@ -217,4 +217,6 @@ public interface IExcelTabService extends BaseService<ExcelTab> {
     void synchronizedPdf(List<TableInfo> tableInfoList, String nodeId, String classify, String contractId, String projectId) throws Exception;
 
     void cancelSample(Long groupId, Long pkeyId, Long contractId, Long sampleId1) throws Exception;
+
+    void expailHtmlInfo(String thmlUrl, Long id, String s) throws Exception ;
 }

+ 5 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/ITextdictInfoService.java

@@ -26,6 +26,8 @@ import org.springblade.manager.vo.TextdictInfoVO;
 import org.springblade.core.mp.base.BaseService;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 
+import java.util.List;
+
 /**
  * 参数信息表 服务类
  *
@@ -54,4 +56,7 @@ public interface ITextdictInfoService extends IService<TextdictInfo> {
     void removeHtmlDefault(String tabId, String colKey);
 
     void saveHtmlDefault(TextdictBy345VO textdictInfo);
+
+    //表单获取电签信息
+    List<TextdictInfoVO> getTextdictListInfoByPkeyId(String tabId,String projectId);
 }

+ 2 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/IWbsTreePrivateService.java

@@ -31,6 +31,8 @@ public interface IWbsTreePrivateService extends BaseService<WbsTreePrivate> {
     // 项目级 表单类型划分
     IPage<TreeNodeVOByTabType> tabTypeLazyTree(IPage<TreeNodeVOByTabType> page, Long parentId, String projectId, String titleName);
 
+    IPage<TreeNodeVOByTabType> tabTypeLazyTreeByProject(IPage<TreeNodeVOByTabType> page, Long parentId, String projectId, String titleName);
+
     // 元素
     IPage<TreeNodeVOByTabType> tabTypeLazyTreeAll(IPage<TreeNodeVOByTabType> page, Long parentId, String titleName,Boolean hasPartFormula);
 

+ 604 - 8
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ExcelTabServiceImpl.java

@@ -14,7 +14,10 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.mixsmart.utils.FormulaUtils;
 import com.mixsmart.utils.ListUtils;
 import com.mixsmart.utils.RegexUtils;
+import com.spire.xls.ExcelPicture;
 import com.spire.xls.FileFormat;
+import com.spire.xls.Worksheet;
+import com.spire.xls.collections.PicturesCollection;
 import lombok.AllArgsConstructor;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang.StringUtils;
@@ -65,10 +68,7 @@ import org.springblade.manager.formula.NodeTable;
 import org.springblade.manager.formula.impl.TableElementConverter;
 import org.springblade.manager.mapper.ExcelTabMapper;
 import org.springblade.manager.service.*;
-import org.springblade.manager.utils.FileUtils;
-import org.springblade.manager.utils.PdfAddContextUtils;
-import org.springblade.manager.utils.PdfAddimgUtil;
-import org.springblade.manager.utils.RandomNumberHolder;
+import org.springblade.manager.utils.*;
 import org.springblade.manager.vo.*;
 import org.springblade.resource.feign.NewIOSSClient;
 import org.springblade.system.cache.ParamCache;
@@ -88,6 +88,7 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.support.DefaultTransactionDefinition;
 
 import java.io.*;
+import java.net.URL;
 import java.nio.file.Files;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -97,6 +98,7 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 import static java.util.stream.Collectors.toMap;
@@ -130,7 +132,8 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
     private final TableInfoServiceImpl tableInfoService;
     private final INodeBaseInfoService nodeBaseInfoService;
     private final TrialSelfInspectionRecordClient trialSelfInspectionRecordClient;
-
+    // excel 解析结构
+    private final IExctabCellService exctabCellService;
 
     @Autowired
     StringRedisTemplate RedisTemplate;
@@ -853,7 +856,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
         int status = callback.getStatus();
         int saved = 0;
         //status=6,表示点击保存按钮 2 关闭保存
-        /*if (status == 3 || status == 6) //MustSave, Corrupted
+        if (status == 3 || status == 6) //MustSave, Corrupted
         {
             //获取url
             String downloadUri = callback.getUrl();
@@ -893,6 +896,8 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
                 excelTab.setHtmlUrl(thmlUrl);
                 baseMapper.updateById(excelTab);
 
+                expailHtmlInfo(thmlUrl, excelTab.getId(), excelTab.getTabType() + "");
+
                 File file2 = new File(dataUrl);
                 if (file2.exists()) {
                     file2.delete();
@@ -902,7 +907,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
                 editCallback.setError(1);
                 e.printStackTrace();
             }
-        }*/
+        }
         return editCallback;
     }
 
@@ -2349,7 +2354,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
                                     anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE);
                                     // 插入图片
                                     Picture pict = drawing.createPicture(anchor, pictureIdx); // 调整图片占单元格百分比的大小,1.0就是100%
-                                    pict.resize(1, 1);
+                                    pict.resize(0.9, 0.9);
                                     FileUtils.imageOrientation(sheet, anchor, new DataVO(x1 - 1, y1 - 1));
                                 }
 
@@ -5630,4 +5635,595 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
         }
         //
     }
+
+    public void expailHtmlInfo(String thmlUrl, Long excelId, String tabType) throws Exception {
+
+        // 读取
+        File file1 = ResourceUtil.getFile(thmlUrl);
+        String htmlString = IoUtil.readToString(new FileInputStream(file1));
+
+        /*
+           解析
+           1 解析样式
+           2 计算坐标
+           3 计算区域位置
+         */
+        // 样式集合
+        Document doc = Jsoup.parse(htmlString);
+        // 解析 style
+        Map<String, String> styleMap = getHtmlStyle(doc);
+        //解析
+        Element table = doc.select("table").first();
+
+        Elements trs = table.select("tr");
+        Element tableinfo = trs.get(0).parent().parent().attr("style", "border-collapse: collapse;");
+
+        // 获取图片信息
+        Elements imgs = doc.select("img");
+
+
+        // 获取总行列数
+        int maxCol = doc.select("Col").size();
+        String[] rowData = new String[trs.size() + 5]; //本来加一的 害怕出现特殊情况 故意 加 5
+
+        // 行的状态
+        boolean index_state = false;
+        // 区域划分表示
+        int xy_type = 1;
+
+        // 解析 excel元素集合
+        List<ExctabCell> colTitle = new ArrayList<>();
+
+//      标题集合信息
+        List<Map<String, String>> zikey = new ArrayList<>();
+        for (int i = 0; i <= trs.size() - 1; i++) {
+            Element tr = trs.get(i);
+            Elements tds = tr.select("td");
+
+            String xyInof = getTrInfo(tds, styleMap, index_state, xy_type, maxCol, i, zikey);
+
+            xy_type = Integer.parseInt(xyInof.split(",")[0]);
+            tr.attr("xy_type", xyInof);
+            index_state = Boolean.parseBoolean(xyInof.split(",")[1]);
+            boolean isMaxCol = Integer.parseInt(xyInof.split(",")[2]) == maxCol;
+
+            boolean istrue = Boolean.parseBoolean(xyInof.split(",")[3]);
+
+
+            // 计算单元格坐标
+            int x = 0;
+
+            for (int j = 0; j < tds.size(); j++) {
+                {
+                    Element data = tds.get(j);
+                    Boolean isText = false;
+                    //判断高度
+                    String style = data.attr("style");
+                    if (StringUtils.isNotBlank(style)) {
+                        int index = style.lastIndexOf(":");
+                        String substring = style.substring(index + 1);
+                        String height = substring.substring(0, substring.length() - 3);
+                        int i1 = 0;
+                        try {
+                            i1 = Integer.parseInt(height);
+                        } catch (NumberFormatException e) {
+                            substring = style.substring(style.lastIndexOf(":", index - 1) + 1);
+                            height = substring.substring(0, substring.indexOf("px;"));
+                            i1 = Integer.parseInt(height);
+                        }
+                        if (i1 > 90) {
+                            isText = true;
+                        }
+                    }
+                    String trHtml = data.html();
+                    int colspan = data.attr("COLSPAN").equals("") ? 0 : Integer.parseInt(data.attr("COLSPAN"));
+                    int rowspan = data.attr("ROWSPAN").equals("") ? 0 : Integer.parseInt(data.attr("ROWSPAN"));
+                    String keyId = data.attr("class");
+                    if (StringUtils.isNotEmpty(keyId)) {
+                        String classInfo = styleMap.get(keyId);
+                        data.removeAttr("class");
+                    }
+
+                    Map<String, String> textObject = new HashMap<>();
+                    // 计算
+                    int x1 = 0;
+                    int x2 = 0;
+                    int y1 = 0;
+                    int y2 = 0;
+
+                    String textInfo = data.text().trim().replaceAll(" ", "");
+
+                    y1 = i + 1;
+                    //x 移位 算法
+                    String getRowInfo = rowData[y1];
+
+                    if (getRowInfo != null) {
+                        String[] dataInfo2 = getRowInfo.split(",");
+
+                        // 排序
+                        int lastMax = 0;
+                        List<String> datax = Arrays.stream(dataInfo2).sorted((a, b) -> Integer.parseInt(a.split(":")[1]) - Integer.parseInt(b.split(":")[1])).collect(Collectors.toList());
+                        List<String> lastdata = new ArrayList<>();
+                        //组合
+                        for (int h = 0; h < datax.size(); h++) {
+                            int mx1 = Integer.parseInt(datax.get(h).split(":")[0]);
+                            int mx2 = Integer.parseInt(datax.get(h).split(":")[1]);
+                            if (lastdata.size() == 0) {
+                                lastdata.add(datax.get(0));
+                            } else {
+                                if (lastMax + 1 == mx1) {
+                                    int minVal = Integer.parseInt(lastdata.get(lastdata.size() - 1).split(":")[0]);
+                                    lastdata.remove(lastdata.size() - 1);
+                                    lastdata.add(minVal + ":" + mx2);
+                                } else {
+                                    lastdata.add(datax.get(h));
+                                }
+                            }
+                            lastMax = mx2;
+                        }
+                        dataInfo2 = lastdata.stream().toArray(String[]::new);
+
+                        // 先逻辑处理 连续时,归一
+                        if ((dataInfo2[0].split(":")[0]).equals("1") && j == 0) {
+                            x = Integer.parseInt(dataInfo2[0].split(":")[1]);
+                        } else {
+                            for (int m = 0; m < dataInfo2.length; m++) {
+                                int mx1 = Integer.parseInt(dataInfo2[m].split(":")[0]);
+                                int mx2 = Integer.parseInt(dataInfo2[m].split(":")[1]);
+                                if ((mx1 - x) == 1 && mx1 > x) {
+                                    x = mx2;
+                                }
+                            }
+                        }
+                    }
+
+                    // X 坐标
+
+
+                    if (tds.size() == 1) {
+                        if (colspan == 0) {
+                            x1 = x + 1;
+                            x2 = x + 1;
+                        } else {
+                            if (x == 0) {
+                                x1 = 1;
+                                x2 = colspan;
+                            } else {
+                                x1 = x + 1;
+                                x2 = x + colspan;
+                            }
+                        }
+                    } else {
+                        if (colspan == 0) {
+                            x1 = x + 1;
+                            x2 = x + 1;
+                            x = x2;
+                        } else {
+                            x1 = x + 1;
+                            x2 = x + colspan;
+                            x = x2;
+                        }
+                    }
+
+
+                    //x y 坐标
+                    if (rowspan == 0) {
+                        y2 = i + 1;
+                    } else {
+                        y2 = i + rowspan;
+                        for (int k = 0; k < rowspan - 1; k++) {
+                            String dataInfo = rowData[k + 2 + i];
+                            if (dataInfo == null) {
+                                dataInfo = x1 + ":" + x2;
+                            } else {
+                                dataInfo = dataInfo + "," + x1 + ":" + x2;
+                                String[] arr = dataInfo.split(",");
+                                //排序
+                                for (int r = 0; r < arr.length - 1; r++) {
+                                    for (int q = arr.length - 2; q >= r; q--) {
+                                        Integer jval = Integer.parseInt(arr[q + 1].split(":")[0]);
+                                        Integer jval1 = Integer.parseInt(arr[q].split(":")[0]);
+                                        if (jval < jval1) {
+                                            String temp = arr[q + 1];
+                                            arr[q + 1] = arr[q];
+                                            arr[q] = temp;
+                                        }
+                                    }
+                                }
+
+                                //组合
+                                String newDataInfo = "";
+                                for (int r = 0; r < arr.length; r++) {
+                                    if (r == 0) {
+                                        newDataInfo = arr[0];
+                                    } else {
+                                        int StrMax = Integer.parseInt(newDataInfo.substring(newDataInfo.lastIndexOf(":") + 1, newDataInfo.length()));
+                                        Integer nowMin = Integer.parseInt(arr[r].split(":")[0]);
+                                        Integer nowMax = Integer.parseInt(arr[r].split(":")[1]);
+                                        if ((StrMax + 1) == nowMin) {
+                                            String lastStr = "";
+                                            if (newDataInfo.indexOf(",") >= 0) {
+                                                lastStr = newDataInfo.substring(newDataInfo.lastIndexOf(",") + 1, newDataInfo.length());
+                                                newDataInfo = newDataInfo.substring(0, newDataInfo.lastIndexOf(","));
+                                            } else {
+                                                lastStr = newDataInfo;
+                                                newDataInfo = "";
+                                            }
+                                            int lastmin = Integer.parseInt(lastStr.split(":")[0]);
+                                            if (StringUtils.isNotEmpty(newDataInfo)) {
+                                                newDataInfo = newDataInfo + "," + lastmin + ":" + nowMax;
+                                            } else {
+                                                newDataInfo = lastmin + ":" + nowMax;
+                                            }
+                                        } else {
+                                            newDataInfo = newDataInfo + "," + nowMin + ":" + nowMax;
+                                        }
+                                    }
+                                }
+                                dataInfo = newDataInfo;
+                            }
+                            rowData[k + 2 + i] = dataInfo;
+                        }
+                    }
+
+                    data.text(textInfo.replaceAll(" ", ""));
+                    if (textInfo.indexOf("□") < 0 && !textInfo.isEmpty() && !(textInfo.equals("/") && textInfo.length() < 2) && !(textInfo.indexOf("年") >= 0 && textInfo.indexOf("月") >= 0 && textInfo.indexOf("日") >= 0) && !textInfo.equals("—") && !textInfo.equals("-")) {  // 标题区域
+                        Map<String, String> dataInfo = new HashMap<String, String>();
+                        dataInfo.put("name", textInfo);
+                        dataInfo.put("x1", x1 + "");
+                        dataInfo.put("x2", x2 + "");
+                        dataInfo.put("y1", y1 + "");
+                        dataInfo.put("y2", y2 + "");
+                        dataInfo.put("xytype", xy_type + "");
+                        if (textInfo.indexOf("/") < 0 || (textInfo.indexOf("/") >= 0 && textInfo.length() > 1)) { // 带/为分割数据
+                            zikey.add(dataInfo);
+                        }
+                    } else { //空行
+                        List<Map<String, String>> left = new ArrayList<>();
+                        List<Map<String, String>> top = new ArrayList<>();
+                        for (int k = 0; k < zikey.size(); k++) {
+                            String name = zikey.get(k).get("name");
+                            int xx1 = Integer.parseInt(zikey.get(k).get("x1"));
+                            int xx2 = Integer.parseInt(zikey.get(k).get("x2"));
+                            int yy1 = Integer.parseInt(zikey.get(k).get("y1"));
+                            int yy2 = Integer.parseInt(zikey.get(k).get("y2"));
+                            int xytype2 = Integer.parseInt(zikey.get(k).get("xytype"));
+
+                            // 左匹配
+                            if (yy1 <= y1 && yy2 >= y2 && xx2 < x1 && xytype2 == xy_type) {
+                                left.add(zikey.get(k));
+                            }
+
+                            //向 上 匹配
+                            if (index_state) {
+                                if (xx1 <= x1 && xx2 >= x2 && yy2 < y1 && xytype2 == xy_type) {
+                                    top.add(zikey.get(k));
+                                }
+                            }
+                        }
+
+                        String inputText = "";
+                        // 特征值赛选 规则
+                        for (int k = 0; k < left.size(); k++) { // 左计算
+                            String name = left.get(k).get("name");
+                            int xx1 = Integer.parseInt(left.get(k).get("x1"));
+                            int xx2 = Integer.parseInt(left.get(k).get("x2"));
+                            int yy1 = Integer.parseInt(left.get(k).get("y1"));
+                            int yy2 = Integer.parseInt(left.get(k).get("y2"));
+
+                            if (!StringUtil.isNumeric(name) && name.length() <= 20) { // 数字不匹配
+                                if (index_state) { // 正向规则匹配
+                                    if (istrue) { // 是否空格等于值
+                                        if (x1 - xx2 <= 1 && y1 == yy2) {
+                                            inputText = name;
+                                        } else {
+                                            inputText += name + "_";
+                                        }
+                                    } else {
+                                        inputText += name + "_";
+                                    }
+                                } else {
+                                    if (x1 - xx2 <= 1 && y1 == yy2) {
+                                        inputText = name;
+                                    }
+                                }
+                            }
+                        }
+
+                        // 特征值赛选 规则
+                        if (top != null && top.size() >= 1) {
+                            for (int k = 0; k < top.size(); k++) { // 向上计算
+                                String name = top.get(k).get("name");
+                                int xx1 = Integer.parseInt(top.get(k).get("x1"));
+                                int xx2 = Integer.parseInt(top.get(k).get("x2"));
+                                int yy1 = Integer.parseInt(top.get(k).get("y1"));
+                                int yy2 = Integer.parseInt(top.get(k).get("y2"));
+                                if (!StringUtil.isNumeric(name) && name.length() <= 20) {
+                                    inputText += name + "_";
+                                }
+                            }
+                        }
+
+                        if (inputText != null && inputText != "" && inputText.indexOf("_") >= 0) {
+                            inputText = inputText.substring(0, inputText.lastIndexOf("_"));
+                        }
+
+                        // 质检表特殊处理匹配
+
+                        String parm = i + "," + j + "," + x1 + "," + x2 + "," + y1 + "," + y2 + ",$event";
+                        // 设置文本信息
+                        ExctabCell exctabCell = new ExctabCell();
+                        if ((textInfo.indexOf("年") >= 0 && textInfo.indexOf("月") >= 0 && textInfo.indexOf("日") >= 0) || inputText.indexOf("日期") >= 0) {
+                            if (inputText.indexOf("日期") >= 0) {
+                                data.empty().append("<el-date-picker type='date' @keyDowns='dateKeydown()' format='YYYY年MM月DD日' value-format='YYYY年MM月DD日' @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;' placeholder='" + inputText + "'> </el-date-picker>");
+                            } else if (textInfo.indexOf("年") >= 0 && textInfo.indexOf("月") >= 0 && textInfo.indexOf("日") >= 0) {
+                                if (inputText.indexOf("专业监理工程师") >= 0) {
+                                    inputText = "专业监理工程师_年月日";
+                                } else if (inputText.indexOf("质检工程师") >= 0) {
+                                    inputText = "质检工程师_年月日";
+                                } else {
+                                    inputText = "年月日";
+                                }
+                            }
+                            data.empty().append("<el-date-picker @keyDowns='dateKeydown()'  type='date' format='YYYY年MM月DD日' value-format='YYYY年MM月DD日' @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;' placeholder='年月日'> </el-date-picker>");
+                            exctabCell.setTextInfo(inputText);
+                            exctabCell.setExctabId(excelId);
+                            exctabCell.setIsDeleted(0);
+                            exctabCell.setXys(i + "_" + j);
+                            colTitle.add(exctabCell);
+                            data.attr("title", inputText);
+
+                        } else if (textInfo.indexOf("□") >= 0) { //多选框
+                            exctabCell.setTextInfo(inputText);
+                            exctabCell.setExctabId(excelId);
+                            exctabCell.setIsDeleted(0);
+                            exctabCell.setXys(i + "_" + j);
+                            colTitle.add(exctabCell);
+                            data.attr("title", inputText);
+                            // 添加多选框
+
+                            String[] cheText = textInfo.split("□");
+                            JSONArray objs = new JSONArray();
+                            if (cheText != null && cheText.length >= 1) {
+                                int key = 1;
+                                for (String keyval : cheText) {
+                                    JSONObject jsonObject = new JSONObject();
+                                    if (StringUtils.isNotEmpty(keyval)) {
+                                        jsonObject.put("key", key);
+                                        jsonObject.put("name", keyval);
+                                        objs.add(jsonObject);
+                                        keyId += 1;
+                                    }
+                                }
+                            } else {
+                                JSONObject jsonObject = new JSONObject();
+                                jsonObject.put("key", "1");
+                                jsonObject.put("name", "");
+                                objs.add(jsonObject);
+                            }
+
+                            String checkbox = "<hc-form-checkbox-group @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft' @keydown.shift.right='keyupShiftRight' :objs='" + objs + "'  @change='checkboxGroupChange' @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " placeholder=''> </hc-form-checkbox-group>";
+                            data.empty().append(checkbox);
+
+                        } else {
+
+                            if (index_state) { // 区域内
+                                if (rowspan >= 1 || isText) {
+                                    data.empty().append("<el-input type='textarea' @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft'  @keydown.shift.right='keyupShiftRight'  @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;'   :rows=" + rowspan * 2 + " placeholder=''> </el-input>");
+                                } else {
+                                    data.empty().append("<el-input type='text' @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft'  @keydown.shift.right='keyupShiftRight'  @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;' placeholder=''> </el-input>");
+                                }
+                            } else if (tabType.equals("100") && i < 2) { //计量特殊添加
+                                data.empty().append("<el-input type='text' @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft'  @keydown.shift.right='keyupShiftRight'  @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;' placeholder=''> </el-input>");
+                            } else { // 区域外
+                                if (j == 0) {
+                                    if (colspan == maxCol && i >= 1) {
+                                        if (rowspan >= 1 || isText) {
+                                            data.empty().append("<el-input @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft' @keydown.shift.right='keyupShiftRight' type='textarea'  @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;'   :rows=" + rowspan * 2 + " placeholder=''> </el-input>");
+                                        } else {
+                                            data.empty().append("<el-input @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft' @keydown.shift.right='keyupShiftRight' type='text' @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;' placeholder=''> </el-input>");
+                                        }
+                                    }
+                                } else {
+                                    Element bforData = tds.get(j - 1);
+                                    if (!bforData.text().isEmpty() || bforData.html().indexOf("hc-form-checkbox-group") >= 0) {
+                                        if (rowspan >= 1 || isText) {
+                                            data.empty().append("<el-input @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft' @keydown.shift.right='keyupShiftRight' type='textarea' @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;'   :rows=" + rowspan * 2 + " placeholder=''> </el-input>");
+                                        } else {
+                                            data.empty().append("<el-input @keydown.shift.up='keyupShiftUp' @keydown.shift.down='keyupShiftDown' @keydown.shift.left='keyupShiftLeft'  @keydown.shift.right='keyupShiftRight' type='text' @contextmenu.prevent.native='contextmenuClick(" + parm + ")'  @mouseup.right='RightClick(" + parm + ")' trIndex=" + i + " tdIndex=" + j + "  x1=" + x1 + " x2=" + x2 + " y1=" + y1 + " y2=" + y2 + " style='width:100%;height:100%;' placeholder=''> </el-input>");
+                                        }
+                                    }
+                                }
+                            }
+
+                            if (!inputText.equals("")) {
+                                exctabCell.setExctabId(excelId);
+                                exctabCell.setTextInfo(inputText);
+                                if (inputText.contains("日期") || inputText.contains("年") || inputText.contains("月") || inputText.contains("日")) {
+                                    //日期
+                                    exctabCell.setTextElementType(4);
+                                } else if (inputText.indexOf("签字") >= 0) {
+                                    exctabCell.setTextElementType(6);
+                                } else {
+                                    //字符串
+                                    exctabCell.setTextElementType(1);
+                                }
+
+                                exctabCell.setIsDeleted(0);
+                                exctabCell.setXys(i + "_" + j);
+                                colTitle.add(exctabCell);
+                            }
+                            data.attr("title", inputText);
+                        }
+                    }
+                }
+            }
+        }
+        //System.out.println(zikey);
+        // 去掉重复的数
+        Map<String, String> groupMap2 = colTitle.stream()
+                .collect(Collectors.groupingBy(ExctabCell::getTextInfo, Collectors.mapping(ExctabCell::getXys, Collectors.joining(","))));
+
+        exctabCellService.DeletExcelByTableId(excelId + "");
+
+        List<ExctabCell> colTitle2 = new ArrayList<>();
+        for (String title : groupMap2.keySet()) {
+            ExctabCell exctabCell = new ExctabCell();
+            exctabCell.setExctabId(excelId);
+            exctabCell.setIsDeleted(0);
+            exctabCell.setTextInfo(title);
+            exctabCell.setCreateTime(new Date());
+
+            if (title.contains("日期") || title.contains("年") || title.contains("月") || title.contains("日")) {
+                //日期
+                exctabCell.setTextElementType(4);
+            } else {
+                //字符串
+                exctabCell.setTextElementType(1);
+            }
+
+            exctabCell.setXys(groupMap2.get(title));
+            colTitle2.add(exctabCell);
+        }
+        exctabCellService.saveBatch(colTitle2);
+
+
+        //对excel 的图片进行操作
+        ExcelTab exceltab = this.getById(excelId);
+        if (exceltab != null) {
+            // 获取excle 的数据
+            String fileUrl = exceltab.getFileUrl();
+            InputStream ossInputStream = CommonUtil.getOSSInputStream(fileUrl);
+            com.spire.xls.Workbook wb = new com.spire.xls.Workbook();
+            wb.loadFromMHtml(ossInputStream);
+            Worksheet sheet = wb.getWorksheets().get(0);
+            PicturesCollection pictures = sheet.getPictures();
+            if (pictures != null && pictures.size() >= 1) {
+                for (int i = 0; i < pictures.size(); i++) {
+                    ExcelPicture pic = pictures.get(i);
+                    int x = pic.getLeftColumn();
+                    int y = pic.getBottomRow();
+                    Elements select = doc.select("el-input[x1=" + x + "][y1=" + y + "]");
+                    System.out.println("xx=--" + x);
+                    System.out.println("yy=--" + y);
+                    if (select != null && select.size() >= 1) {
+                        Element element = select.get(0);
+                        Element elementP = element.parent();
+                        element.remove();
+                        Element imgele = imgs.get(i);
+                        imgele.removeAttr("class");
+                        elementP.append(imgele.toString());
+                    }
+                }
+            }
+
+            ossInputStream.close();
+        }
+        // 移除图片
+        imgs.remove();
+        // 保存
+        exceltab.setIsDeleted(8);
+        this.saveOrUpdate(exceltab);
+        File writefile = new File(thmlUrl);
+        FileUtil.writeToFile(writefile, doc.html(), Boolean.parseBoolean("UTF-8"));
+    }
+
+    // 获取解析样式
+    public static Map<String, String> getHtmlStyle(Document doc) {
+        Map<String, String> styleMap = new HashMap<>();
+        Element style = doc.select("style").first();
+        Matcher cssMatcher = Pattern.compile("(\\w+)\\s*[{]([^}]+)[}]").matcher(style.html());
+        while (cssMatcher.find()) {
+            styleMap.put(cssMatcher.group(1), cssMatcher.group(2));
+        }
+        return styleMap;
+    }
+
+    //计算区域坐标
+    public static String getTrInfo(Elements tds, Map<String, String> styleMap, boolean index_state, Integer xy_type, int maxCol, int y, List<Map<String, String>> zikey) {
+
+        int x_width = 0;
+        int y_width = 0;
+        int text_width = 0;
+        int width = 0;
+
+        int null_count = 0;
+        int val_count = 0;
+
+        boolean istrue = true;
+
+        // 上 tr 长度
+
+        List<Map<String, String>> maxList = zikey.stream().filter(map -> Integer.parseInt(map.get("y1")) <= y && y <= Integer.parseInt(map.get("y2"))).collect(Collectors.toList());
+
+        int top1_max = 0;
+        if (maxList != null && maxList.size() >= 1) {
+            top1_max = maxList.stream().mapToInt(m -> Integer.parseInt(m.get("x2"))).max().getAsInt();
+        }
+
+
+        //区域计算
+        for (int j = 0; j < tds.size(); j++) {
+            Element data = tds.get(j);
+            String keyId = data.attr("class");
+            int colspan = data.attr("COLSPAN").equals("") ? 1 : Integer.parseInt(data.attr("COLSPAN"));
+            if (colspan == 0) {
+                colspan = data.attr("colspan").equals("") ? 1 : Integer.parseInt(data.attr("colspan"));
+            }
+            String classInfo = styleMap.get(keyId);
+            String textInfo = data.text().trim().replaceAll(" ", "");
+
+            if (classInfo == null) {
+                classInfo = data.attr("style");
+            } else {
+                data.removeAttr("class");
+                data.attr("style", styleMap.get(keyId).replaceAll("break-word", "inherit"));
+            }
+
+            // 计算线开始
+            if (classInfo.indexOf("border-left-style") >= 0 && classInfo.indexOf("border-top-style") >= 0 && classInfo.indexOf("border-right-style") >= 0) {
+                x_width += colspan;
+            }
+
+            // 计算结束
+            if (classInfo.indexOf("border-left-style") < 0 && (classInfo.indexOf("border-top-style") < 0 || classInfo.indexOf("border-top-style") >= 0) && classInfo.indexOf("border-bottom-style") < 0 && classInfo.indexOf("border-right-style") < 0) {
+                y_width += colspan;
+            }
+
+            String name = data.text();
+            if (!name.isEmpty()) {
+                text_width += colspan;
+            }
+            width += colspan;
+
+            if (!textInfo.isEmpty() && !(textInfo.equals("/") && textInfo.length() <= 2) && !(textInfo.indexOf("年") >= 0 && textInfo.indexOf("月") >= 0 && textInfo.indexOf("日") >= 0) && !textInfo.equals("—") && !textInfo.equals("-")) {  // 标题区域
+                val_count++;
+            } else {
+                null_count++;
+            }
+        }
+
+        // 在区域内
+        //  System.out.println(index_state+"——"+y+"__"+x_width);
+
+        if (index_state) {
+            //是否需要改变
+            if (maxCol == y_width) { // 是否结束区域值
+                index_state = false;
+                xy_type += 1;
+            }
+            if (maxCol == text_width && top1_max != text_width) { // 是否区域开始时
+                xy_type += 1;
+            }
+        } else { // 区域外
+            if (maxCol == x_width) {
+                index_state = true;
+                xy_type += 1;
+            }
+        }
+
+        // 空是否等于值的个数
+        istrue = null_count == val_count && width == maxCol;
+        return xy_type + "," + index_state + "," + width + "," + istrue;
+    }
 }

+ 1 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaServiceImpl.java

@@ -297,6 +297,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
     public FormulaDataBlock findFdb(TableElementConverter tec) {
         /*查找第一个包含分项评定子节点的父节点*/
         Long ancestor = findFirstParentId(tec);
+        ancestor = ancestor == null || ancestor == 0 ? tec.getCurrentNode().getParentId() : ancestor;
         FormulaDataBlock fdb = this.formulaDataBlockService.queryOption(tec.getContractId(), ancestor, 0);
         if (fdb == null) {
             fdb = new FormulaDataBlock();

+ 6 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/TextdictInfoServiceImpl.java

@@ -227,6 +227,12 @@ public class TextdictInfoServiceImpl extends ServiceImpl<TextdictInfoMapper, Tex
 
 
 
+    }
+
+    @Override
+    public List<TextdictInfoVO> getTextdictListInfoByPkeyId(String tabId,String projectId) {
+        return  baseMapper.getTextdictListInfoByPkeyId(tabId,projectId);
+
     }
 
     @Override

+ 3 - 7
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsFormElementServiceImpl.java

@@ -23,11 +23,7 @@ import org.springblade.manager.dto.WbsFormElementDTO2;
 import org.springblade.manager.entity.*;
 import org.springblade.manager.excel.WbsFormElementBatchExcel;
 import org.springblade.manager.excel.WbsFormElementExcel;
-import org.springblade.manager.mapper.TableInfoMapper;
-import org.springblade.manager.mapper.WbsFormElementMapper;
-import org.springblade.manager.mapper.WbsTreeMapper;
-import org.springblade.manager.mapper.WbsTreePrivateMapper;
-import org.springblade.manager.service.IExcelTabService;
+import org.springblade.manager.mapper.*;
 import org.springblade.manager.service.ITableInfoService;
 import org.springblade.manager.service.IWbsFormElementService;
 import org.springblade.manager.utils.FileUtils;
@@ -61,7 +57,7 @@ public class WbsFormElementServiceImpl extends BaseServiceImpl<WbsFormElementMap
 
     private final ITableInfoService tableInfoService;
 
-    private final IExcelTabService excelTabService;
+    private final ExcelTabMapper excelTabMapper;
 
     private final JdbcTemplate jdbcTemplate;
 
@@ -851,7 +847,7 @@ public class WbsFormElementServiceImpl extends BaseServiceImpl<WbsFormElementMap
                         }
 
                         // 绑定excel 与实体表的关系
-                        excelTabService.update(Wrappers.<ExcelTab>lambdaUpdate()
+                        excelTabMapper.update(null,Wrappers.<ExcelTab>lambdaUpdate()
                                 .set(ExcelTab::getTabId, formElementDTO.getInitTableId())
                                 .eq(ExcelTab::getId, formElementDTO.getExcelTabId())
                         );

+ 7 - 2
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeContractServiceImpl.java

@@ -643,8 +643,13 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
                 }
                 //检验附表以外的排序有问题的表单修改
                 if(resultTab.getIsTypePrivatePid()!=null){
-                    String sql="select IFNULL(sort,1000) from m_wbs_tree_private where p_key_id="+resultTab.getIsTypePrivatePid()+" and is_deleted=0";
-                    Integer sort = jdbcTemplate.queryForObject(sql,Integer.class);
+                    Integer sort=1000;
+                    try{
+                        String sql="select IFNULL(sort,1000) from m_wbs_tree_private where p_key_id="+resultTab.getIsTypePrivatePid()+" and is_deleted=0";
+                        sort= jdbcTemplate.queryForObject(sql,Integer.class);
+                    }catch (Exception e){
+                        System.out.println("未找到p_key_id"+resultTab.getIsTypePrivatePid()+"的数据");
+                    }
                     if(sort!=null&&sort!=1000&&!Objects.equals(sort, resultTab.getSort())){
                         resultTab.setSort(sort);
                         String sql1="update m_wbs_tree_contract set sort="+sort+" where p_key_id="+resultTab.getPKeyId();

+ 8 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreePrivateServiceImpl.java

@@ -295,6 +295,14 @@ public class WbsTreePrivateServiceImpl extends BaseServiceImpl<WbsTreePrivateMap
         return page.setRecords(baseMapper.tabTypeLazyTree(page, parentId, projectId, titleName));
     }
 
+    @Override
+    public IPage<TreeNodeVOByTabType> tabTypeLazyTreeByProject(IPage<TreeNodeVOByTabType> page, Long parentId, String projectId, String titleName) {
+        if ((parentId + "").equals("12345678910")) {
+            page.setSize(100);
+        }
+        return page.setRecords(baseMapper.tabTypeLazyTreeByProject(page, parentId, projectId, titleName));
+    }
+
     @Override
     public IPage<TreeNodeVOByTabType> tabTypeLazyTreeAll(IPage<TreeNodeVOByTabType> page, Long parentId, String titleName, Boolean hasPartFormula) {
         if (parentId == null) {

+ 3 - 3
blade-service/blade-manager/src/main/java/org/springblade/manager/utils/PdfAddimgUtil.java

@@ -56,12 +56,12 @@ public class PdfAddimgUtil {
                 PDFIndexInfo pdfIndexInfo = positions.get(i);
                 float[] position = pdfIndexInfo.getDataInfo();
                 TextdictInfo textdictInfo = textMap.get(Func.toLong(pdfIndexInfo.getPkeyid()));
-                float pyzbx = 0;
+                float pyzbx = -13;
                 float pyzby = 0;
                 String type ="2";
                 if(textdictInfo!=null){
-                    pyzbx = Func.toFloat(textdictInfo.getPyzbx());
-                    pyzby = Func.toFloat(textdictInfo.getPyzby());
+                    pyzbx = Func.toFloat(textdictInfo.getPyzbx()) +pyzbx;
+                    pyzby = Func.toFloat(textdictInfo.getPyzby()) +pyzby;
                     type = textdictInfo.getType()+"";
                 }
                 gaizhang(pdfFile, new File(pdfUrl), (int) position[0], position[1], position[2], signImg,pyzbx,pyzby,type);

+ 17 - 13
blade-service/blade-user/src/main/java/org/springblade/system/user/service/impl/UserServiceImpl.java

@@ -58,6 +58,7 @@ import org.springblade.system.user.mapper.UserMapper;
 import org.springblade.system.user.service.IUserDeptService;
 import org.springblade.system.user.service.IUserOauthService;
 import org.springblade.system.user.service.IUserService;
+import org.springblade.system.user.util.ComplexStringComparator;
 import org.springblade.system.user.util.FileUtils;
 import org.springblade.system.user.util.ForestNodeMerger;
 import org.springblade.system.user.vo.InformationQueryBIMVO;
@@ -2236,7 +2237,8 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
         if (obj != null) {
             List<Long> resultPKeyId = new ArrayList<>();
             List<WbsTreeContractLazyVO> nodeAll = this.getNodeAll(obj.getContractId());
-            this.recursionGetChildNodesPkeyIds(nodeAll, Collections.singletonList(obj.getId()), resultPKeyId, lowestNodesPkeyIds);
+            Map<Long, List<WbsTreeContractLazyVO>> map = nodeAll.stream().collect(Collectors.groupingBy(WbsTreeContractLazyVO::getParentId));
+            this.recursionGetChildNodesPkeyIds(map, Collections.singletonList(obj.getId()), resultPKeyId, lowestNodesPkeyIds);
             if (resultPKeyId.size() > 0) {
                 return resultPKeyId;
             }
@@ -2245,19 +2247,21 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
     }
 
     private void recursionGetChildNodesPkeyIds
-            (List<WbsTreeContractLazyVO> nodeAll, List<Long> ids, List<Long> resultPKeyId, List<Long> lowestNodesPkeyIds) {
+            (Map<Long, List<WbsTreeContractLazyVO>> nodeAll, List<Long> ids, List<Long> resultPKeyId, List<Long> lowestNodesPkeyIds) {
         if (ids.size() > 0) {
-            List<WbsTreeContractLazyVO> sortedFilteredNodes = nodeAll.stream()
-                    .filter(f -> ids.contains(f.getParentId()))
-                    .sorted(Comparator.comparing(WbsTreeContractLazyVO::getSort, Comparator.nullsLast(Integer::compareTo))
-                            .thenComparing(WbsTreeContractLazyVO::getFullName, Comparator.nullsLast(String::compareTo))
-                            .thenComparing(WbsTreeContractLazyVO::getCreateTime, Comparator.nullsLast(Comparator.naturalOrder())))
-                    .collect(Collectors.toList());
-
             Map<Long, List<WbsTreeContractLazyVO>> filteredNodesMap = new LinkedHashMap<>();
-            sortedFilteredNodes.forEach(node -> {
-                Long parentId = node.getParentId();
-                filteredNodesMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(node);
+            ids.forEach(id -> {
+                List<WbsTreeContractLazyVO> list = nodeAll.get(id);
+                if (list != null && !list.isEmpty()) {
+                    List<WbsTreeContractLazyVO> collect = list.stream().sorted(Comparator.comparing(WbsTreeContractLazyVO::getSort, Comparator.nullsFirst(Comparator.naturalOrder()))
+                                    .thenComparing(new ComplexStringComparator<>(obj -> obj.getFullName() != null ? obj.getFullName() : ""))
+                                    .thenComparing(WbsTreeContractLazyVO::getCreateTime, Comparator.nullsLast(Comparator.reverseOrder())))
+                            .collect(Collectors.toList());
+                    collect.forEach(node -> {
+                        Long parentId = node.getParentId();
+                        filteredNodesMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(node);
+                    });
+                }
             });
 
             for (Long id : ids) {
@@ -2267,9 +2271,9 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
                         if (node.getHasChildren().equals(0)) {
                             lowestNodesPkeyIds.add(node.getPKeyId());
                         }
+                        this.recursionGetChildNodesPkeyIds(nodeAll, Collections.singletonList(node.getId()), resultPKeyId, lowestNodesPkeyIds);
                         resultPKeyId.add(node.getPKeyId());
                     }
-                    this.recursionGetChildNodesPkeyIds(nodeAll, nodes.stream().map(WbsTreeContractLazyVO::getId).collect(Collectors.toList()), resultPKeyId, lowestNodesPkeyIds);
                 }
             }
         }