Commit 510cf6f8b6c7131a90bebcb3c32639be326d01a0
1 parent
9ce96e13
feat(gis-road): 导出GIS道路Excel时实现同道路多绿地合并单元格功能
- 引入FastExcelFactory替代原有Excel工具类 - 添加OnceAbsoluteMergeStrategy用于单元格合并策略 - 集成ColumnWidthMatchStyleStrategy和SelectSheetWriteHandler - 实现已关联道路按道路名称分组合并显示逻辑 - 区分已关联与未关联数据,分别处理导出格式 - 新增mergedGisPlotArea字段用于显示合并后的绿地面积 - 优化文件名生成规则,统一使用xls格式输出 - 实现位置信息拼接逻辑,处理起点终点描述格式化
Showing
2 changed files
with
115 additions
and
23 deletions
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisRoadController.java
| 1 | 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 | 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 | 8 | import org.slf4j.Logger; |
| 5 | 9 | import org.slf4j.LoggerFactory; |
| 6 | 10 | import org.springframework.web.bind.annotation.*; |
| ... | ... | @@ -14,9 +18,12 @@ import io.swagger.v3.oas.annotations.Operation; |
| 14 | 18 | import jakarta.validation.*; |
| 15 | 19 | import jakarta.servlet.http.*; |
| 16 | 20 | import java.io.IOException; |
| 21 | +import java.net.URLEncoder; | |
| 22 | +import java.nio.charset.StandardCharsets; | |
| 17 | 23 | import java.time.LocalDate; |
| 18 | 24 | import java.time.format.DateTimeFormatter; |
| 19 | 25 | import java.util.*; |
| 26 | +import java.util.stream.Collectors; | |
| 20 | 27 | |
| 21 | 28 | import com.zteits.urbanops.framework.common.pojo.PageResult; |
| 22 | 29 | import com.zteits.urbanops.framework.common.pojo.CommonResult; |
| ... | ... | @@ -63,40 +70,120 @@ public class GisRoadController { |
| 63 | 70 | } |
| 64 | 71 | |
| 65 | 72 | @GetMapping("/export-excel") |
| 66 | - @Operation(summary = "导出 GIS 道路 Excel") | |
| 73 | + @Operation(summary = "导出 GIS 道路 Excel(同道路多绿地合并单元格)") | |
| 67 | 74 | @PreAuthorize("@ss.hasPermission('garden:gis-road:export')") |
| 68 | 75 | @ApiAccessLog(operateType = EXPORT) |
| 69 | 76 | public void exportGisRoadExcel(@Valid GisRoadPageReqVO pageReqVO, |
| 70 | 77 | HttpServletResponse response) throws IOException { |
| 71 | 78 | pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); |
| 72 | 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 | 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 | 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 | 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 | 25 | @JsonSerialize(using = TwoDecimalFloatSerializer.class) |
| 26 | 26 | private Float gisPlotArea; |
| 27 | 27 | |
| 28 | + @Schema(description = "十谱绿地合并面积") | |
| 29 | + @ExcelProperty("十谱绿地合并面积(㎡)") | |
| 30 | + @JsonSerialize(using = TwoDecimalFloatSerializer.class) | |
| 31 | + private Float mergedGisPlotArea; | |
| 32 | + | |
| 28 | 33 | @Schema(description = "道路面积") |
| 29 | 34 | @ExcelProperty("道路面积(㎡)") |
| 30 | 35 | @JsonSerialize(using = TwoDecimalFloatSerializer.class) | ... | ... |