diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/wt/mapper/SdWtvtRMapper.java b/backend/src/main/java/com/yfd/platform/qgc_env/wt/mapper/SdWtvtRMapper.java index 8a514c00..8c4fb63d 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/wt/mapper/SdWtvtRMapper.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/wt/mapper/SdWtvtRMapper.java @@ -7,6 +7,7 @@ import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import java.util.List; +import java.util.Map; /** * 垂向水温年份数据Mapper @@ -28,4 +29,20 @@ public interface SdWtvtRMapper extends BaseMapper { "ORDER BY STCD, TO_CHAR(TM, 'yyyy') DESC" + "") List getWtrvDefaultYear(@Param("stcd") String stcd); + + /** + * 查询各测站水深对应的高程(HGT),按 STCD、WTHG 分组取最大值 + */ + @Select("") + List> selectDepthElevation(@Param("stcdList") List stcdList); } diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdWtvtdayS.java b/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdWtvtdayS.java new file mode 100644 index 00000000..5cb01453 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdWtvtdayS.java @@ -0,0 +1,27 @@ +package com.yfd.platform.qgc_export.domain; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * SD_WTVTDAY_S 垂向水温日统计表 + */ +@Data +@TableName("SD_WTVTDAY_S") +public class SdWtvtdayS implements Serializable { + private static final long serialVersionUID = 1L; + + private String id; + private String stcd; + private Date dt; + private BigDecimal wthg; // 水深 + private BigDecimal vwt; // 水温 + private String recordUser; + private Date recordTime; + private Date modifyTime; + private Integer isDeleted; +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdWtvtdaySMapper.java b/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdWtvtdaySMapper.java new file mode 100644 index 00000000..dad13645 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdWtvtdaySMapper.java @@ -0,0 +1,28 @@ +package com.yfd.platform.qgc_export.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yfd.platform.qgc_export.domain.SdWtvtdayS; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + * SD_WTVTDAY_S 垂向水温日数据 Mapper + */ +public interface SdWtvtdaySMapper extends BaseMapper { + + /** + * 查询月内每日垂向水温数据(含 EXTRACT(DAY FROM DT)),用于按指定日期筛选 + */ + List> selectDailyData(@Param("stcdList") List stcdList, + @Param("startTime") String startTime, + @Param("endTime") String endTime); + + /** + * 查询月均垂向水温,按 STCD、WTHG 分组 + */ + List> selectMonthlyAvg(@Param("stcdList") List stcdList, + @Param("startTime") String startTime, + @Param("endTime") String endTime); +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/processor/RwtProcessor.java b/backend/src/main/java/com/yfd/platform/qgc_export/processor/RwtProcessor.java new file mode 100644 index 00000000..930d91b9 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/processor/RwtProcessor.java @@ -0,0 +1,60 @@ +package com.yfd.platform.qgc_export.processor; + +import com.yfd.platform.qgc_env.wt.mapper.SdWtvtRMapper; +import com.yfd.platform.qgc_export.mapper.SdWtvtdaySMapper; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +/** + * 垂向水温(RWT) 处理器 — 双表头格式,按水深分层展示 + *

+ * STTP: WTVT, 使用 SD_WTVT_R / SD_WTVTDAY_S 表 + */ +@Component +public class RwtProcessor implements ExportIndicatorProcessor { + + private SdWtvtRMapper sdWtvtRMapper; + private SdWtvtdaySMapper sdWtvtdaySMapper; + + public void setMappers(SdWtvtRMapper rMapper, SdWtvtdaySMapper sMapper) { + this.sdWtvtRMapper = rMapper; + this.sdWtvtdaySMapper = sMapper; + } + + public SdWtvtRMapper getSdWtvtRMapper() { return sdWtvtRMapper; } + public SdWtvtdaySMapper getSdWtvtdaySMapper() { return sdWtvtdaySMapper; } + + @Override + public String getFieldName() { + return "rwt"; + } + + @Override + public String getSheetName() { + return "垂向水温"; + } + + @Override + public String getSttpType() { + return "WTVT"; + } + + @Override + public int getDecimalPlaces() { + return 2; + } + + @Override + public int getOrder() { + return 11; + } + + @Override + public List queryRawData(List stcds, Date startTime, Date endTime, boolean isDaily) { + // RWT 使用自定义 Sheet 构建,不走标准数据查询流程 + return Collections.emptyList(); + } +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/service/impl/QgcExportServiceImpl.java b/backend/src/main/java/com/yfd/platform/qgc_export/service/impl/QgcExportServiceImpl.java index 05089b7e..51069e4b 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_export/service/impl/QgcExportServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/qgc_export/service/impl/QgcExportServiceImpl.java @@ -13,6 +13,9 @@ import com.yfd.platform.qgc_export.mapper.SdHydropwdaySMapper; import com.yfd.platform.qgc_export.mapper.SdQecRMapper; import com.yfd.platform.qgc_export.mapper.SdQecdaySMapper; import com.yfd.platform.qgc_export.mapper.StationMappingMapper; +import com.yfd.platform.qgc_export.mapper.MsAlongdetBMapper; +import com.yfd.platform.qgc_env.wt.mapper.SdWtvtRMapper; +import com.yfd.platform.qgc_export.mapper.SdWtvtdaySMapper; import com.yfd.platform.qgc_export.processor.*; import com.yfd.platform.qgc_export.service.IQgcExportService; import com.yfd.platform.qgc_eng.eq.entity.vo.EngEqRuleVo; @@ -142,6 +145,17 @@ public class QgcExportServiceImpl implements IQgcExportService { @Resource private EngEqDataService engEqDataService; + @Resource + private MsAlongdetBMapper msAlongdetBMapper; + + @Resource + private SdWtvtRMapper sdWtvtRMapper; + @Resource + private SdWtvtdaySMapper sdWtvtdaySMapper; + + @Resource + private RwtProcessor rwtProcessor; + private static final SimpleDateFormat SDF_TIME = new SimpleDateFormat("dd日 HH时"); private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -192,6 +206,8 @@ public class QgcExportServiceImpl implements IQgcExportService { // QEC 自定义三栏达标率格式 if (processor instanceof QecProcessor) { rows = buildQecSheetRows(mr, stationOrder, stationRes); + } else if (processor instanceof RwtProcessor) { + rows = buildRwtSheetRows(mr, stationOrder, stationRes); } else { rows = buildSheetRows(processor, mr, stationOrder, stationRes); } @@ -206,6 +222,15 @@ public class QgcExportServiceImpl implements IQgcExportService { headers.add(title); for (int i = 1; i < 6; i++) headers.add(""); } + } else if (processor instanceof RwtProcessor) { + // 垂向水温双表头:测站名称 | 水深 | 5日 | | 15日 | | 25日 | | 月均 | + headers = new ArrayList<>(); + headers.add("测站名称"); + headers.add("水深(m)"); + headers.add("5日"); headers.add(""); + headers.add("15日"); headers.add(""); + headers.add("25日"); headers.add(""); + headers.add("月均"); headers.add(""); } else { // 仅包含当前处理器 STTP 类型有对应测站的电站 headers = new ArrayList<>(); @@ -228,6 +253,31 @@ public class QgcExportServiceImpl implements IQgcExportService { new int[]{0, 0, 6, 11}, // 水利部 new int[]{0, 0, 12, 17} // 多年平均 )); + } else if (processor instanceof RwtProcessor) { + // 垂向水温双表头合并单元格 + sd.setHeaderRowCount(1); + List regions = new ArrayList<>(Arrays.asList( + new int[]{0, 1, 0, 0}, // 测站名称 header + new int[]{0, 1, 1, 1}, // 水深(m) header + new int[]{0, 0, 2, 3}, // 5日 + new int[]{0, 0, 4, 5}, // 15日 + new int[]{0, 0, 6, 7}, // 25日 + new int[]{0, 0, 8, 9} // 月均 + )); + // 数据行测站名称相同合并(rows[0] 是子标题行,数据从 rows[1] 开始,sheet 行号 = 2 + i) + int dataStartRow = 2; // headers(1) + subheader(1) + for (int i = 1; i < rows.size(); ) { + String name = rows.get(i).get(0).toString(); + int j = i + 1; + while (j < rows.size() && name.equals(rows.get(j).get(0).toString())) { + j++; + } + if (j - i > 1) { + regions.add(new int[]{dataStartRow + i - 1, dataStartRow + j - 2, 0, 0}); + } + i = j; + } + sd.setMergedRegions(regions); } sheets.add(sd); if ("WQ".equals(sttp)) { @@ -494,6 +544,179 @@ public class QgcExportServiceImpl implements IQgcExportService { *

* 返回的 rows[0] 是子标题行(|达标数|总数|达标率(%)|达标数|总数|达标率(%)|),后续为电站数据行。 */ + + // ======================== RWT 垂向水温 ======================== + + /** + * 构建垂向水温(RWT) Sheet 数据 — 双表头格式,按测站和水深分层 + */ + private List> buildRwtSheetRows(MonthRange mr, List stationOrder, + StationResolution stationRes) { + List stcdList = new ArrayList<>(); + // WTVT 测站→显示名 映射 + Map stcdDisplayMap = new LinkedHashMap<>(); + for (String rstcd : stationOrder) { + String stcd = stationRes.getStcd(rstcd, "WTVT"); + if (stcd != null) { + stcdList.add(stcd); + String displayName = stationRes.getDisplayName(rstcd, "WTVT"); + stcdDisplayMap.put(stcd, StrUtil.isNotBlank(displayName) ? displayName : stcd); + } + } + if (stcdList.isEmpty()) return null; + + String startStr = SDF.format(mr.getStart()); + String endStr = SDF.format(mr.getEnd()); + + // 1. 查询日数据(含 EXTRACT(DAY FROM DT)) + List> dailyData = sdWtvtdaySMapper.selectDailyData(stcdList, startStr, endStr); + // 2. 查询月均 + List> monthlyData = sdWtvtdaySMapper.selectMonthlyAvg(stcdList, startStr, endStr); + // 3. 查询高程 + List> elevationData = sdWtvtRMapper.selectDepthElevation(stcdList); + + // 按 (STCD, WTHG) 构建日水温索引 + Map> dayVwtMap = new LinkedHashMap<>(); + Map wthgOrderKey = new LinkedHashMap<>(); + for (Map row : dailyData) { + String stcd = (String) row.get("STCD"); + Object wthgObj = row.get("WTHG"); + Object vwtObj = row.get("VWT"); + Object dayNumObj = row.get("DAY_NUM"); + if (stcd == null || wthgObj == null || vwtObj == null || dayNumObj == null) continue; + + String wthg = formatNumber(wthgObj); + int dayNum = ((Number) dayNumObj).intValue(); + BigDecimal vwt = toBigDecimal(vwtObj); + + String key = stcd + "|" + wthg; + dayVwtMap.computeIfAbsent(key, k -> new LinkedHashMap<>()) + .putIfAbsent(String.valueOf(dayNum), vwt); + wthgOrderKey.putIfAbsent(wthg, toBigDecimal(wthgObj)); + } + + // 月均索引 + Map monthlyAvgMap = new LinkedHashMap<>(); + for (Map row : monthlyData) { + String stcd = (String) row.get("STCD"); + Object wthgObj = row.get("WTHG"); + Object vwtObj = row.get("VWT"); + if (stcd == null || wthgObj == null || vwtObj == null) continue; + + String wthg = formatNumber(wthgObj); + String key = stcd + "|" + wthg; + monthlyAvgMap.put(key, toBigDecimal(vwtObj)); + wthgOrderKey.putIfAbsent(wthg, toBigDecimal(wthgObj)); + } + + // 高程索引 + Map elevationMap = new LinkedHashMap<>(); + for (Map row : elevationData) { + String stcd = (String) row.get("STCD"); + Object wthgObj = row.get("WTHG"); + Object hgtObj = row.get("HGT"); + if (stcd == null || wthgObj == null || hgtObj == null) continue; + + String wthg = formatNumber(wthgObj); + elevationMap.put(stcd + "|" + wthg, toBigDecimal(hgtObj)); + } + + // 收集所有 (STCD, WTHG) 组合,按测站顺序和 WTHG 升序排列 + Set allKeys = new LinkedHashSet<>(); + allKeys.addAll(dayVwtMap.keySet()); + allKeys.addAll(monthlyAvgMap.keySet()); + + List sortedKeys = new ArrayList<>(); + for (String stcd : stcdList) { + String prefix = stcd + "|"; + List stcdKeys = new ArrayList<>(); + for (String key : allKeys) { + if (key.startsWith(prefix)) { + stcdKeys.add(key.substring(prefix.length())); + } + } + stcdKeys.sort(Comparator.comparing(k -> wthgOrderKey.getOrDefault(k, BigDecimal.ZERO))); + for (String wthg : stcdKeys) { + sortedKeys.add(prefix + wthg); + } + } + + // 构建数据行 + List> rows = new ArrayList<>(); + // 子标题行(Row 0) + List subHeaderRow = new ArrayList<>(); + subHeaderRow.add(""); subHeaderRow.add(""); + subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)"); + subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)"); + subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)"); + subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)"); + rows.add(subHeaderRow); + + for (String key : sortedKeys) { + int sep = key.indexOf('|'); + String stcd = key.substring(0, sep); + String wthg = key.substring(sep + 1); + + Map dayMap = dayVwtMap.getOrDefault(key, Collections.emptyMap()); + BigDecimal elev = elevationMap.get(key); + BigDecimal monthlyAvg = monthlyAvgMap.get(key); + + List row = new ArrayList<>(); + row.add(stcdDisplayMap.getOrDefault(stcd, stcd)); + row.add(formatNumberCell(wthg)); + row.add(formatTemp(dayMap.get("5"))); + row.add(formatElev(elev)); + row.add(formatTemp(dayMap.get("15"))); + row.add(formatElev(elev)); + row.add(formatTemp(dayMap.get("25"))); + row.add(formatElev(elev)); + row.add(formatTemp(monthlyAvg)); + row.add(formatElev(elev)); + rows.add(row); + } + + return rows; + } + + private String formatNumberCell(String numStr) { + BigDecimal val = toBigDecimal(numStr); + return val != null ? stripTrailingZeros(val) : numStr; + } + + private String formatTemp(BigDecimal val) { + if (val == null) return ""; + return val.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString(); + } + + private String formatElev(BigDecimal val) { + if (val == null) return ""; + return stripTrailingZeros(val); + } + + private String formatNumber(Object obj) { + if (obj instanceof BigDecimal) return stripTrailingZeros((BigDecimal) obj); + if (obj instanceof Number) return String.valueOf(obj); + return obj.toString(); + } + + private BigDecimal toBigDecimal(Object val) { + if (val instanceof BigDecimal) return (BigDecimal) val; + if (val instanceof Number) return BigDecimal.valueOf(((Number) val).doubleValue()); + if (val instanceof String) { + try { + return new BigDecimal((String) val); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + private String stripTrailingZeros(BigDecimal val) { + if (val == null) return ""; + return val.stripTrailingZeros().toPlainString(); + } + private List> buildWqComplianceSheet( MonthRange mr, List stationOrder, StationResolution stationRes) { @@ -659,6 +882,8 @@ public class QgcExportServiceImpl implements IQgcExportService { processorMap.put("qo", qoProcessor); // 生态流量(STTP=ENG,仅标准格式无日变幅) processorMap.put("qec", qecProcessor); + // 垂向水温(STTP=WTVT, 双表头分层格式) + processorMap.put("rwt", rwtProcessor); // WQ 别名:展开为全部水质指标 List expanded = new ArrayList<>(); @@ -721,6 +946,8 @@ public class QgcExportServiceImpl implements IQgcExportService { ((HydropwIndicatorProcessor) p).setMappers(sdHydropwRMapper, sdHydropwdaySMapper); } else if (p instanceof QecProcessor) { ((QecProcessor) p).setMappers(sdQecRMapper, sdQecdaySMapper); + } else if (p instanceof RwtProcessor) { + ((RwtProcessor) p).setMappers(sdWtvtRMapper, sdWtvtdaySMapper); } result.add(p); } diff --git a/backend/src/main/resources/mapper/qgc_export/SdWtvtdaySMapper.xml b/backend/src/main/resources/mapper/qgc_export/SdWtvtdaySMapper.xml new file mode 100644 index 00000000..f92b48b7 --- /dev/null +++ b/backend/src/main/resources/mapper/qgc_export/SdWtvtdaySMapper.xml @@ -0,0 +1,42 @@ + + + + + + + + + + +