Commit 510cf6f8b6c7131a90bebcb3c32639be326d01a0

Authored by 王彪总
1 parent 9ce96e13

feat(gis-road): 导出GIS道路Excel时实现同道路多绿地合并单元格功能

- 引入FastExcelFactory替代原有Excel工具类
- 添加OnceAbsoluteMergeStrategy用于单元格合并策略
- 集成ColumnWidthMatchStyleStrategy和SelectSheetWriteHandler
- 实现已关联道路按道路名称分组合并显示逻辑
- 区分已关联与未关联数据,分别处理导出格式
- 新增mergedGisPlotArea字段用于显示合并后的绿地面积
- 优化文件名生成规则,统一使用xls格式输出
- 实现位置信息拼接逻辑,处理起点终点描述格式化
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisRoadController.java
1 package com.zteits.urbanops.module.garden.controller.admin.gis; 1 package com.zteits.urbanops.module.garden.controller.admin.gis;
2 2
  3 +import cn.idev.excel.FastExcelFactory;
  4 +import cn.idev.excel.write.merge.OnceAbsoluteMergeStrategy;
3 import com.zteits.urbanops.framework.common.pojo.PageParam; 5 import com.zteits.urbanops.framework.common.pojo.PageParam;
  6 +import com.zteits.urbanops.framework.excel.core.handler.ColumnWidthMatchStyleStrategy;
  7 +import com.zteits.urbanops.framework.excel.core.handler.SelectSheetWriteHandler;
4 import org.slf4j.Logger; 8 import org.slf4j.Logger;
5 import org.slf4j.LoggerFactory; 9 import org.slf4j.LoggerFactory;
6 import org.springframework.web.bind.annotation.*; 10 import org.springframework.web.bind.annotation.*;
@@ -14,9 +18,12 @@ import io.swagger.v3.oas.annotations.Operation; @@ -14,9 +18,12 @@ import io.swagger.v3.oas.annotations.Operation;
14 import jakarta.validation.*; 18 import jakarta.validation.*;
15 import jakarta.servlet.http.*; 19 import jakarta.servlet.http.*;
16 import java.io.IOException; 20 import java.io.IOException;
  21 +import java.net.URLEncoder;
  22 +import java.nio.charset.StandardCharsets;
17 import java.time.LocalDate; 23 import java.time.LocalDate;
18 import java.time.format.DateTimeFormatter; 24 import java.time.format.DateTimeFormatter;
19 import java.util.*; 25 import java.util.*;
  26 +import java.util.stream.Collectors;
20 27
21 import com.zteits.urbanops.framework.common.pojo.PageResult; 28 import com.zteits.urbanops.framework.common.pojo.PageResult;
22 import com.zteits.urbanops.framework.common.pojo.CommonResult; 29 import com.zteits.urbanops.framework.common.pojo.CommonResult;
@@ -63,40 +70,120 @@ public class GisRoadController { @@ -63,40 +70,120 @@ public class GisRoadController {
63 } 70 }
64 71
65 @GetMapping("/export-excel") 72 @GetMapping("/export-excel")
66 - @Operation(summary = "导出 GIS 道路 Excel") 73 + @Operation(summary = "导出 GIS 道路 Excel(同道路多绿地合并单元格)")
67 @PreAuthorize("@ss.hasPermission('garden:gis-road:export')") 74 @PreAuthorize("@ss.hasPermission('garden:gis-road:export')")
68 @ApiAccessLog(operateType = EXPORT) 75 @ApiAccessLog(operateType = EXPORT)
69 public void exportGisRoadExcel(@Valid GisRoadPageReqVO pageReqVO, 76 public void exportGisRoadExcel(@Valid GisRoadPageReqVO pageReqVO,
70 HttpServletResponse response) throws IOException { 77 HttpServletResponse response) throws IOException {
71 pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); 78 pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
72 List<GisRoadRespVO> list = gisRoadService.getGisRoadPage(pageReqVO).getList(); 79 List<GisRoadRespVO> list = gisRoadService.getGisRoadPage(pageReqVO).getList();
  80 +
  81 + // 分离:已关联 vs 未关联(未关联的不参与合并)
  82 + List<GisRoadRespVO> relatedList = list.stream()
  83 + .filter(vo -> vo.getGisIsRelated() != null && vo.getGisIsRelated() == 1)
  84 + .sorted(Comparator.comparing(GisRoadRespVO::getRoadName, Comparator.nullsLast(String::compareTo)))
  85 + .collect(Collectors.toList());
  86 + List<GisRoadRespVO> unrelatedList = list.stream()
  87 + .filter(vo -> vo.getGisIsRelated() == null || vo.getGisIsRelated() != 1)
  88 + .collect(Collectors.toList());
  89 +
  90 + // 已关联的按 roadName 分组
  91 + Map<String, List<GisRoadRespVO>> grouped = relatedList.stream()
  92 + .collect(Collectors.groupingBy(
  93 + vo -> vo.getRoadName() != null ? vo.getRoadName() : "",
  94 + LinkedHashMap::new, Collectors.toList()));
  95 +
73 List<GisRoadExportVO> exportList = new ArrayList<>(); 96 List<GisRoadExportVO> exportList = new ArrayList<>();
74 - for (GisRoadRespVO vo : list) {  
75 - GisRoadExportVO exportVO = new GisRoadExportVO();  
76 - exportVO.setGisPlotName(vo.getGisPlotName());  
77 - exportVO.setRoadName(vo.getRoadName());  
78 - exportVO.setGisPlotArea(vo.getGisPlotArea());  
79 - exportVO.setTotalArea(vo.getTotalArea());  
80 - exportVO.setGisIsRelatedStr(vo.getGisIsRelated() != null && vo.getGisIsRelated() == 1 ? "已关联" : "未关联");  
81 - exportVO.setGisDiffArea(vo.getGisDiffArea());  
82 - exportVO.setCompanyName(vo.getCompanyName());  
83 - exportVO.setDeptName(vo.getDeptName());  
84 - // 位置信息:起点描述 ~ 终点描述  
85 - String positionInfo = "";  
86 - if (vo.getStartingRemark() != null) {  
87 - positionInfo += vo.getStartingRemark(); 97 + List<OnceAbsoluteMergeStrategy> mergeStrategies = new ArrayList<>();
  98 + int rowIdx = 1;
  99 +
  100 + // ===== 处理已关联的(合并) =====
  101 + for (List<GisRoadRespVO> group : grouped.values()) {
  102 + int startRow = rowIdx;
  103 + float sumArea = 0f;
  104 + String roadName = group.get(0).getRoadName();
  105 + Float totalArea = group.get(0).getTotalArea();
  106 + String companyName = group.get(0).getCompanyName();
  107 + String deptName = group.get(0).getDeptName();
  108 +
  109 + String posInfo = "";
  110 + if (group.get(0).getStartingRemark() != null) posInfo += group.get(0).getStartingRemark();
  111 + if (group.get(0).getEndRemark() != null) {
  112 + if (!posInfo.isEmpty()) posInfo += " ~ ";
  113 + posInfo += group.get(0).getEndRemark();
  114 + }
  115 +
  116 + for (GisRoadRespVO vo : group) {
  117 + GisRoadExportVO ev = new GisRoadExportVO();
  118 + ev.setGisPlotName(vo.getGisPlotName());
  119 + ev.setRoadName(roadName);
  120 + ev.setGisPlotArea(vo.getGisPlotArea());
  121 + if (vo.getGisPlotArea() != null) sumArea += vo.getGisPlotArea();
  122 + ev.setTotalArea(totalArea);
  123 + ev.setGisIsRelatedStr("已关联");
  124 + ev.setCompanyName(companyName);
  125 + ev.setDeptName(deptName);
  126 + ev.setPositionInfo(posInfo.isEmpty() ? null : posInfo);
  127 + exportList.add(ev);
  128 + rowIdx++;
  129 + }
  130 +
  131 + int endRow = rowIdx - 1;
  132 + float diff = sumArea - (totalArea != null ? totalArea : 0f);
  133 + if (group.size() > 1) {
  134 + exportList.get(startRow - 1).setMergedGisPlotArea(sumArea);
  135 + exportList.get(startRow - 1).setGisDiffArea(diff);
  136 + mergeStrategies.add(new OnceAbsoluteMergeStrategy(startRow, endRow, 1, 1));
  137 + mergeStrategies.add(new OnceAbsoluteMergeStrategy(startRow, endRow, 3, 3));
  138 + mergeStrategies.add(new OnceAbsoluteMergeStrategy(startRow, endRow, 4, 4));
  139 + mergeStrategies.add(new OnceAbsoluteMergeStrategy(startRow, endRow, 5, 5));
  140 + mergeStrategies.add(new OnceAbsoluteMergeStrategy(startRow, endRow, 6, 6));
  141 + mergeStrategies.add(new OnceAbsoluteMergeStrategy(startRow, endRow, 7, 7));
  142 + mergeStrategies.add(new OnceAbsoluteMergeStrategy(startRow, endRow, 8, 8));
  143 + mergeStrategies.add(new OnceAbsoluteMergeStrategy(startRow, endRow, 9, 9));
88 } 144 }
  145 + exportList.get(startRow - 1).setMergedGisPlotArea(sumArea);
  146 + exportList.get(startRow - 1).setGisDiffArea(diff);
  147 + }
  148 +
  149 + // ===== 处理未关联的(不合并,各自独立行) =====
  150 + for (GisRoadRespVO vo : unrelatedList) {
  151 + GisRoadExportVO ev = new GisRoadExportVO();
  152 + ev.setGisPlotName(vo.getGisPlotName());
  153 + ev.setRoadName(vo.getRoadName());
  154 + ev.setGisPlotArea(vo.getGisPlotArea());
  155 + ev.setMergedGisPlotArea(vo.getGisPlotArea());
  156 + ev.setTotalArea(vo.getTotalArea());
  157 + ev.setGisIsRelatedStr("未关联");
  158 + ev.setGisDiffArea(vo.getGisDiffArea());
  159 + ev.setCompanyName(vo.getCompanyName());
  160 + ev.setDeptName(vo.getDeptName());
  161 + String posInfo = "";
  162 + if (vo.getStartingRemark() != null) posInfo += vo.getStartingRemark();
89 if (vo.getEndRemark() != null) { 163 if (vo.getEndRemark() != null) {
90 - if (!positionInfo.isEmpty()) {  
91 - positionInfo += " ~ ";  
92 - }  
93 - positionInfo += vo.getEndRemark(); 164 + if (!posInfo.isEmpty()) posInfo += " ~ ";
  165 + posInfo += vo.getEndRemark();
94 } 166 }
95 - exportVO.setPositionInfo(positionInfo.isEmpty() ? null : positionInfo);  
96 - exportList.add(exportVO); 167 + ev.setPositionInfo(posInfo.isEmpty() ? null : posInfo);
  168 + exportList.add(ev);
  169 + rowIdx++;
  170 + }
  171 +
  172 + // 使用 FastExcelFactory 写 Excel,逐个注册 merge 策略
  173 + String filename = "十谱绿地核对" + LocalDate.now()
  174 + .format(DateTimeFormatter.ofPattern("yyyyMMdd")) + ".xls";
  175 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
  176 + response.setCharacterEncoding("utf-8");
  177 + response.setHeader("Content-disposition",
  178 + "attachment;filename*=utf-8''" + URLEncoder.encode(filename, StandardCharsets.UTF_8));
  179 +
  180 + var builder = FastExcelFactory.write(response.getOutputStream(), GisRoadExportVO.class)
  181 + .registerWriteHandler(new ColumnWidthMatchStyleStrategy())
  182 + .registerWriteHandler(new SelectSheetWriteHandler(GisRoadExportVO.class));
  183 + for (OnceAbsoluteMergeStrategy ms : mergeStrategies) {
  184 + builder.registerWriteHandler(ms);
97 } 185 }
98 - String filename = "十谱绿地核对" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + ".xls";  
99 - ExcelUtils.write(response, filename, "数据", GisRoadExportVO.class, exportList); 186 + builder.sheet("数据").doWrite(exportList);
100 } 187 }
101 188
102 @PostMapping("/associate") 189 @PostMapping("/associate")
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadExportVO.java
@@ -25,6 +25,11 @@ public class GisRoadExportVO { @@ -25,6 +25,11 @@ public class GisRoadExportVO {
25 @JsonSerialize(using = TwoDecimalFloatSerializer.class) 25 @JsonSerialize(using = TwoDecimalFloatSerializer.class)
26 private Float gisPlotArea; 26 private Float gisPlotArea;
27 27
  28 + @Schema(description = "十谱绿地合并面积")
  29 + @ExcelProperty("十谱绿地合并面积(㎡)")
  30 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  31 + private Float mergedGisPlotArea;
  32 +
28 @Schema(description = "道路面积") 33 @Schema(description = "道路面积")
29 @ExcelProperty("道路面积(㎡)") 34 @ExcelProperty("道路面积(㎡)")
30 @JsonSerialize(using = TwoDecimalFloatSerializer.class) 35 @JsonSerialize(using = TwoDecimalFloatSerializer.class)