diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdQecR.java b/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdQecR.java new file mode 100644 index 00000000..b4cecc24 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdQecR.java @@ -0,0 +1,50 @@ +package com.yfd.platform.qgc_export.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 生态流量监测数据表(小时级) + * 对应 Oracle 表: QGC_REFA.SD_QEC_R + */ +@Data +@TableName("SD_QEC_R") +public class SdQecR implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.INPUT) + private String id; + + /** 水电工程编码 */ + private String stcd; + + /** 数据识别时间 */ + private Date tm; + + /** 生态流量 (m³/s) */ + private BigDecimal qec; + + /** AI识别生态流量值 */ + private BigDecimal waterflow; + + /** 生态流量泄放方式 */ + private String tpqec; + + /** 生态流量是否达标 */ + private Integer sfdb; + + private String recordUser; + private Date recordTime; + private String modifyUser; + private Date modifyTime; + private Integer isDeleted; + private String deleteUser; + private Date deleteTime; +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdQecdayS.java b/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdQecdayS.java new file mode 100644 index 00000000..81f88638 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdQecdayS.java @@ -0,0 +1,50 @@ +package com.yfd.platform.qgc_export.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 生态流量监测数据日统计表 + * 对应 Oracle 表: QGC_REFA.SD_QECDAY_S + */ +@Data +@TableName("SD_QECDAY_S") +public class SdQecdayS implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.INPUT) + private String id; + + /** 水电工程编码 */ + private String stcd; + + /** 数据识别时间 */ + private Date dt; + + /** 生态流量 (m³/s) */ + private BigDecimal qec; + + /** AI识别生态流量值 */ + private BigDecimal waterflow; + + /** 生态流量泄放方式 */ + private String tpqec; + + /** 生态流量是否达标 */ + private Integer sfdb; + + private String recordUser; + private Date recordTime; + private String modifyUser; + private Date modifyTime; + private Integer isDeleted; + private String deleteUser; + private Date deleteTime; +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/domain/StationMapping.java b/backend/src/main/java/com/yfd/platform/qgc_export/domain/StationMapping.java index c8f58ee6..e360e38d 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_export/domain/StationMapping.java +++ b/backend/src/main/java/com/yfd/platform/qgc_export/domain/StationMapping.java @@ -31,4 +31,8 @@ public class StationMapping implements Serializable { /** 测站名称 */ @TableField("STNM") private String stnm; + + /** 所属水电基地名称 */ + @TableField("BASE_NAME") + private String baseName; } diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdQecRMapper.java b/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdQecRMapper.java new file mode 100644 index 00000000..8f6e1077 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdQecRMapper.java @@ -0,0 +1,25 @@ +package com.yfd.platform.qgc_export.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yfd.platform.qgc_export.domain.SdQecR; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + * SD_QEC_R 生态流量小时数据 Mapper + */ +public interface SdQecRMapper extends BaseMapper { + + List> selectRawData(@Param("stcdList") List stcdList, + @Param("startTime") String startTime, + @Param("endTime") String endTime); + + /** + * 查询生态流量达标数据(小时级),返回三种 SFDB 字段 + */ + List> selectComplianceData(@Param("stcdList") List stcdList, + @Param("startTime") String startTime, + @Param("endTime") String endTime); +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdQecdaySMapper.java b/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdQecdaySMapper.java new file mode 100644 index 00000000..608acf4f --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdQecdaySMapper.java @@ -0,0 +1,25 @@ +package com.yfd.platform.qgc_export.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yfd.platform.qgc_export.domain.SdQecdayS; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + * SD_QECDAY_S 生态流量日数据 Mapper + */ +public interface SdQecdaySMapper extends BaseMapper { + + List> selectRawData(@Param("stcdList") List stcdList, + @Param("startTime") String startTime, + @Param("endTime") String endTime); + + /** + * 查询生态流量达标数据(日级),返回三种 SFDB 字段 + */ + List> selectComplianceData(@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/QecProcessor.java b/backend/src/main/java/com/yfd/platform/qgc_export/processor/QecProcessor.java new file mode 100644 index 00000000..cdf2470f --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/processor/QecProcessor.java @@ -0,0 +1,86 @@ +package com.yfd.platform.qgc_export.processor; + +import com.yfd.platform.qgc_export.mapper.SdQecRMapper; +import com.yfd.platform.qgc_export.mapper.SdQecdaySMapper; + +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * 生态流量(QEC) 处理器 + *

+ * 数据来源: SD_QEC_R / SD_QECDAY_S,STTP="ENG"。 + */ +@Component +public class QecProcessor implements ExportIndicatorProcessor { + + private SdQecRMapper sdQecRMapper; + private SdQecdaySMapper sdQecdaySMapper; + + private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + public void setMappers(SdQecRMapper rMapper, SdQecdaySMapper sMapper) { + this.sdQecRMapper = rMapper; + this.sdQecdaySMapper = sMapper; + } + + @Override + public String getFieldName() { + return "QEC"; + } + + @Override + public String getSheetName() { + return "生态流量"; + } + + @Override + public String getSttpType() { + return "ENG"; + } + + @Override + public int getDecimalPlaces() { + return 3; + } + + @Override + public int getOrder() { + return 37; + } + + @Override + public List queryRawData(List stcds, Date startTime, Date endTime, boolean isDaily) { + String startStr = SDF.format(startTime); + String endStr = SDF.format(endTime); + + List> rawList; + if (isDaily) { + rawList = sdQecdaySMapper.selectRawData(stcds, startStr, endStr); + } else { + rawList = sdQecRMapper.selectRawData(stcds, startStr, endStr); + } + + List rows = new ArrayList<>(); + if (rawList != null) { + for (Map map : rawList) { + String stcd = (String) map.get("STCD"); + Date tm = (Date) map.get(isDaily ? "DT" : "TM"); + BigDecimal value = toBigDecimal(map.get("VALUE")); + if (stcd != null && tm != null && value != null) { + rows.add(new IndicatorDataRow(stcd, tm, value)); + } + } + } + return rows; + } + + private BigDecimal toBigDecimal(Object val) { + if (val instanceof BigDecimal) return (BigDecimal) val; + if (val instanceof Number) return BigDecimal.valueOf(((Number) val).doubleValue()); + return null; + } +} 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 cd3a3c41..bb1a2e9f 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 @@ -1,5 +1,6 @@ package com.yfd.platform.qgc_export.service.impl; +import cn.hutool.core.util.StrUtil; import com.yfd.platform.qgc_export.domain.StationMapping; import com.yfd.platform.qgc_export.mapper.SdRiverRMapper; import com.yfd.platform.qgc_export.mapper.SdRiverdaySMapper; @@ -9,6 +10,8 @@ import com.yfd.platform.qgc_export.mapper.SdWtrvRMapper; import com.yfd.platform.qgc_export.mapper.SdWtrvdaySMapper; import com.yfd.platform.qgc_export.mapper.SdHydropwRMapper; 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.processor.*; import com.yfd.platform.qgc_export.service.IQgcExportService; @@ -126,7 +129,16 @@ public class QgcExportServiceImpl implements IQgcExportService { @Resource private QoProcessor qoProcessor; + // QEC 生态流量相关(STTP=ENG) + @Resource + private SdQecRMapper sdQecRMapper; + @Resource + private SdQecdaySMapper sdQecdaySMapper; + @Resource + private QecProcessor qecProcessor; + private static final SimpleDateFormat SDF_TIME = new SimpleDateFormat("dd日 HH时"); + private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /** 基础汇总指标(7个) */ private static final List BASE_LABELS = Collections.unmodifiableList( @@ -161,6 +173,7 @@ public class QgcExportServiceImpl implements IQgcExportService { List stationOrder = new ArrayList<>(stationRes.displayNames.keySet()); // 5. 每月一个 ExcelData + String timestamp = String.valueOf(System.currentTimeMillis()); List excelList = new ArrayList<>(); for (String month : months) { MonthRange mr = buildMonthRange(month); @@ -168,21 +181,53 @@ public class QgcExportServiceImpl implements IQgcExportService { List sheets = new ArrayList<>(); for (ExportIndicatorProcessor processor : processors) { - List> rows = buildSheetRows(processor, mr, stationOrder, stationRes); + List> rows; + // QEC 自定义三栏达标率格式 + if (processor instanceof QecProcessor) { + rows = buildQecSheetRows(mr, stationOrder, stationRes); + } else { + rows = buildSheetRows(processor, mr, stationOrder, stationRes); + } if (rows != null) { - // 按当前处理器 STTP 类型获取对应的测站名称 + List headers; String sttp = processor.getSttpType(); - List headers = new ArrayList<>(); - headers.add("日期"); - for (String rstcd : stationOrder) { - headers.add(stationRes.getDisplayName(rstcd, sttp)); + if (processor instanceof QecProcessor) { + // 分部标题作为列标题(合并单元格) + String[] sectionTitles = {"生态流量达标率(环保部)", "生态流量达标率(水利部)", "生态流量达标率(多年平均)"}; + headers = new ArrayList<>(); + for (String title : sectionTitles) { + headers.add(title); + for (int i = 1; i < 6; i++) headers.add(""); + } + } else { + // 仅包含当前处理器 STTP 类型有对应测站的电站 + headers = new ArrayList<>(); + headers.add("日期"); + for (String rstcd : stationOrder) { + if (stationRes.getStcd(rstcd, sttp) != null) { + String displayName = stationRes.getDisplayName(rstcd, sttp); + if (StrUtil.isNotBlank(displayName)) { + headers.add(displayName); + } + } + } } - sheets.add(new ExportZipUtil.SheetData(processor.getSheetName(), headers, rows)); + ExportZipUtil.SheetData sd = new ExportZipUtil.SheetData(processor.getSheetName(), headers, rows); + if (processor instanceof QecProcessor) { + // 分部标题合并单元格(Row 0 是 headers),子标题行用 subHeaderStyle + sd.setHeaderRowCount(1); + sd.setMergedRegions(Arrays.asList( + new int[]{0, 0, 0, 5}, // 环保部 + new int[]{0, 0, 6, 11}, // 水利部 + new int[]{0, 0, 12, 17} // 多年平均 + )); + } + sheets.add(sd); } } if (!sheets.isEmpty()) { - excelList.add(new ExportZipUtil.ExcelData(month, sheets)); + excelList.add(new ExportZipUtil.ExcelData(month + "统计数据_" + timestamp, sheets)); } } @@ -248,9 +293,9 @@ public class QgcExportServiceImpl implements IQgcExportService { for (int d = 1; d <= daysInMonth; d++) { List row = new ArrayList<>(); row.add(String.format("%02d", d)); - for (String rstcd : stationOrder) { + for (String rstcd : rstcdToStcd.keySet()) { String stcd = rstcdToStcd.get(rstcd); - Map dayMap = stcd != null ? dailyByStcd.get(stcd) : null; + Map dayMap = dailyByStcd.get(stcd); BigDecimal val = dayMap != null ? dayMap.get(d) : null; row.add(formatOrEmpty(val, decimals)); } @@ -262,7 +307,7 @@ public class QgcExportServiceImpl implements IQgcExportService { List row = new ArrayList<>(); row.add(labels.get(i)); - for (String rstcd : stationOrder) { + for (String rstcd : rstcdToStcd.keySet()) { Aggregation agg = aggMap.get(rstcd); switch (i) { case 0: row.add(formatOrEmpty(agg != null ? agg.getDailyMin() : null, decimals)); break; @@ -283,6 +328,133 @@ public class QgcExportServiceImpl implements IQgcExportService { return rows; } + // ======================== QEC 生态流量三栏达标率 Sheet ======================== + + /** + * 构建生态流量三栏达标率 Sheet 的数据行(不含标题行,标题由调用方放入 headers)。 + *

+ * 返回的 rows[0] 是子标题行(电站名称|所属水电基地|...),后续为电站数据行。 + */ + private List> buildQecSheetRows( + MonthRange mr, List stationOrder, StationResolution stationRes) { + + String sttp = "ENG"; + Map rstcdToStcd = new LinkedHashMap<>(); + for (String rstcd : stationOrder) { + String stcd = stationRes.getStcd(rstcd, sttp); + if (stcd != null) { + rstcdToStcd.put(rstcd, stcd); + } + } + if (rstcdToStcd.isEmpty()) { + log.warn("生态流量未找到 STTP=ENG 的测站数据"); + return null; + } + + List uniqueStcds = rstcdToStcd.values().stream().distinct().toList(); + + // 查询日达标数据 + 小时达标数据 + String startStr = SDF.format(mr.getStart()); + String endStr = SDF.format(mr.getEnd()); + List> dailyList = sdQecdaySMapper.selectComplianceData(uniqueStcds, startStr, endStr); + List> hourlyList = sdQecRMapper.selectComplianceData(uniqueStcds, startStr, endStr); + + // 按 STCD 分组计算达标率 + Map statsByStcd = new LinkedHashMap<>(); + for (String stcd : uniqueStcds) { + ComplianceStats s = new ComplianceStats(); + List> dayForStcd = dailyList.stream() + .filter(m -> stcd.equals(m.get("STCD"))).toList(); + s.dailyHb = calcComplianceRate(dayForStcd, "SFDB"); + s.dailySl = calcComplianceRate(dayForStcd, "MWR_SFDB"); + s.dailyDn = calcComplianceRate(dayForStcd, "AVQ_SFDB"); + + List> hourForStcd = hourlyList.stream() + .filter(m -> stcd.equals(m.get("STCD"))).toList(); + s.hourlyHb = calcComplianceRate(hourForStcd, "SFDB"); + s.hourlySl = calcComplianceRate(hourForStcd, "MWR_SFDB"); + s.hourlyDn = calcComplianceRate(hourForStcd, "AVQ_SFDB"); + + statsByStcd.put(stcd, s); + } + + // ======== Row 0: 子标题行 ======== + List subHeaders = Arrays.asList("电站名称", "所属水电基地", "生态流量限值", "日达标率", "小时达标率", "备注"); + List subRow = new ArrayList<>(); + subRow.addAll(subHeaders); + subRow.addAll(subHeaders); + subRow.addAll(subHeaders); + + // ======== 数据行 ======== + List> rows = new ArrayList<>(); + rows.add(subRow); // 子标题作为第一行 + + for (String rstcd : stationOrder) { + String stcd = rstcdToStcd.get(rstcd); + String displayName = stationRes.getDisplayName(rstcd, sttp); + String baseName = stationRes.getBaseName(rstcd, sttp); + ComplianceStats s = stcd != null ? statsByStcd.get(stcd) : null; + + List row = new ArrayList<>(); + // 环保部 + row.add(displayName != null ? displayName : rstcd); + row.add(baseName); + row.add(""); // 生态流量限值(暂空) + row.add(s != null ? s.dailyHb : ""); + row.add(s != null ? s.hourlyHb : ""); + row.add(""); // 备注(暂空) + // 水利部 + row.add(displayName != null ? displayName : rstcd); + row.add(baseName); + row.add(""); + row.add(s != null ? s.dailySl : ""); + row.add(s != null ? s.hourlySl : ""); + row.add(""); + // 多年平均 + row.add(displayName != null ? displayName : rstcd); + row.add(baseName); + row.add(""); + row.add(s != null ? s.dailyDn : ""); + row.add(s != null ? s.hourlyDn : ""); + row.add(""); + + rows.add(row); + } + + return rows; + } + + /** + * 计算达标率:SFDB=1 达标, SFDB=0 不达标, SFDB=2/3 不参与计算 + */ + private String calcComplianceRate(List> records, String sfdbField) { + long total = 0; + long compliant = 0; + for (Map m : records) { + Object v = m.get(sfdbField); + if (v == null) continue; + int sfdb = toInt(v); + if (sfdb == 0 || sfdb == 1) { + total++; + if (sfdb == 1) compliant++; + } + } + if (total == 0) return ""; + BigDecimal rate = BigDecimal.valueOf(compliant * 100.0 / total); + return rate.setScale(1, RoundingMode.HALF_UP).toString() + "%"; + } + + private int toInt(Object v) { + if (v instanceof Number) return ((Number) v).intValue(); + try { return Integer.parseInt(v.toString()); } catch (Exception e) { return -1; } + } + + /** 分部达标统计 */ + private static class ComplianceStats { + String dailyHb, dailySl, dailyDn; + String hourlyHb, hourlySl, hourlyDn; + } + // ======================== 指标处理器匹配 ======================== /** @@ -308,6 +480,8 @@ public class QgcExportServiceImpl implements IQgcExportService { // 出入库流量(STTP=ENG,仅标准格式无日变幅) processorMap.put("qi", qiProcessor); processorMap.put("qo", qoProcessor); + // 生态流量(STTP=ENG,仅标准格式无日变幅) + processorMap.put("qec", qecProcessor); // WQ 别名:展开为全部水质指标 List expanded = new ArrayList<>(); @@ -368,6 +542,8 @@ public class QgcExportServiceImpl implements IQgcExportService { ((WqIndicatorProcessor) p).setMappers(sdWqRMapper, sdWqdaySMapper); } else if (p instanceof HydropwIndicatorProcessor) { ((HydropwIndicatorProcessor) p).setMappers(sdHydropwRMapper, sdHydropwdaySMapper); + } else if (p instanceof QecProcessor) { + ((QecProcessor) p).setMappers(sdQecRMapper, sdQecdaySMapper); } result.add(p); } @@ -455,6 +631,16 @@ public class QgcExportServiceImpl implements IQgcExportService { } } result.sttpStcdMap.put(rstcd, sttpMap); + + // STTP → BASE_NAME 映射(所属水电基地) + Map baseNames = new LinkedHashMap<>(); + for (StationMapping m : rows) { + String sttp = m.getSttp(); + if (sttp != null && m.getBaseName() != null && !baseNames.containsKey(sttp)) { + baseNames.put(sttp, m.getBaseName()); + } + } + result.baseNameMap.put(rstcd, baseNames); } log.info("电站映射解析完成,RSTCD数量={}, STTP类型={}", result.displayNames.size(), sttpTypes); @@ -628,9 +814,9 @@ public class QgcExportServiceImpl implements IQgcExportService { for (int d = 1; d <= daysInMonth; d++) { List row = new ArrayList<>(); row.add(String.format("%02d", d)); - for (String rstcd : stationOrder) { + for (String rstcd : rstcdToStcd.keySet()) { String stcd = rstcdToStcd.get(rstcd); - Map dayMap = stcd != null ? variationByStcd.get(stcd) : null; + Map dayMap = variationByStcd.get(stcd); BigDecimal val = dayMap != null ? dayMap.get(d) : null; row.add(formatOrEmpty(val, decimals)); } @@ -641,7 +827,7 @@ public class QgcExportServiceImpl implements IQgcExportService { for (int i = 0; i < VARIATION_LABELS.size(); i++) { List row = new ArrayList<>(); row.add(VARIATION_LABELS.get(i)); - for (String rstcd : stationOrder) { + for (String rstcd : rstcdToStcd.keySet()) { BigDecimal[] agg = variationAgg.get(rstcd); row.add(formatOrEmpty(agg != null ? agg[i] : null, decimals)); } @@ -680,6 +866,8 @@ public class QgcExportServiceImpl implements IQgcExportService { final Map> sttpDisplayNames = new LinkedHashMap<>(); /** STTP → STCD 映射,RSTCD → {STTP → STCD} */ final Map> sttpStcdMap = new LinkedHashMap<>(); + /** 所属水电基地名称,RSTCD → {STTP → BASE_NAME} */ + final Map> baseNameMap = new LinkedHashMap<>(); /** 获取默认测站名称 */ String getDisplayName(String rstcd) { @@ -696,7 +884,8 @@ public class QgcExportServiceImpl implements IQgcExportService { String name = inner.get(sttp); if (name != null) return name; } - return getDisplayName(rstcd); + return null; +// return getDisplayName(rstcd); } /** @@ -707,6 +896,16 @@ public class QgcExportServiceImpl implements IQgcExportService { if (inner == null || inner.isEmpty()) return null; return inner.get(sttp); } + + /** 获取指定 STTP 的水电基地名称 */ + String getBaseName(String rstcd, String sttp) { + Map inner = baseNameMap.get(rstcd); + if (inner != null && sttp != null) { + String name = inner.get(sttp); + if (name != null) return name; + } + return ""; + } } @lombok.Data diff --git a/backend/src/main/java/com/yfd/platform/utils/ExportZipUtil.java b/backend/src/main/java/com/yfd/platform/utils/ExportZipUtil.java index e0583236..6670a6af 100644 --- a/backend/src/main/java/com/yfd/platform/utils/ExportZipUtil.java +++ b/backend/src/main/java/com/yfd/platform/utils/ExportZipUtil.java @@ -75,6 +75,23 @@ public class ExportZipUtil { private List headers; /** 数据行集合(每行是一个 Object 列表,与 headers 一一对应) */ private List> rows; + /** + * 需要合并的区域列表,每项 int[4] = {firstRow, lastRow, firstCol, lastCol}(0-based)。 + * 在数据写入后由 fillSheet 统一合并。 + */ + private List mergedRegions; + /** + * rows 中前 headerRowCount 行使用双层标题样式: + * 第 0 行用 headerStyle,第 1 行用 subHeaderStyle,以此类推。 + * 默认 0 表示全部行使用 dataStyle。 + */ + private int headerRowCount; + + public SheetData(String sheetName, List headers, List> rows) { + this.sheetName = sheetName; + this.headers = headers; + this.rows = rows; + } } /** @@ -438,23 +455,41 @@ public class ExportZipUtil { // ---- 写数据行 ---- if (rows != null) { - for (List rowData : rows) { + CellStyle subHeaderStyle = createSubHeaderStyle(workbook); + for (int r = 0; r < rows.size(); r++) { + List rowData = rows.get(r); Row dataRow = sheet.createRow(currentRow++); + int hc = sheetData.getHeaderRowCount(); + CellStyle rowStyle; + if (r < hc) { + // 多层标题:第0行=headerStyle, 第1行=subHeaderStyle, 后续=subHeaderStyle + rowStyle = r == 0 ? headerStyle : subHeaderStyle; + } else { + rowStyle = dataStyle; + } for (int i = 0; i < Math.min(rowData.size(), colCount); i++) { Cell cell = dataRow.createCell(i); Object value = rowData.get(i); setCellValue(cell, value); - cell.setCellStyle(dataStyle); + cell.setCellStyle(rowStyle); } } } + // ---- 合并单元格 ---- + if (sheetData.getMergedRegions() != null) { + for (int[] region : sheetData.getMergedRegions()) { + sheet.addMergedRegion(new CellRangeAddress(region[0], region[1], region[2], region[3])); + } + } + // ---- 自动调整列宽 ---- autoSizeColumns(sheet, colCount); - // 冻结首行(表头固定) + // 冻结表头行 if (currentRow > 1) { - sheet.createFreezePane(0, 1); + int freezeRows = 1 + sheetData.getHeaderRowCount(); + sheet.createFreezePane(0, Math.min(freezeRows, currentRow - 1)); } } diff --git a/backend/src/main/resources/mapper/qgc_export/SdQecRMapper.xml b/backend/src/main/resources/mapper/qgc_export/SdQecRMapper.xml new file mode 100644 index 00000000..90df1d34 --- /dev/null +++ b/backend/src/main/resources/mapper/qgc_export/SdQecRMapper.xml @@ -0,0 +1,39 @@ + + + + + + + + + + diff --git a/backend/src/main/resources/mapper/qgc_export/SdQecdaySMapper.xml b/backend/src/main/resources/mapper/qgc_export/SdQecdaySMapper.xml new file mode 100644 index 00000000..1d7da108 --- /dev/null +++ b/backend/src/main/resources/mapper/qgc_export/SdQecdaySMapper.xml @@ -0,0 +1,39 @@ + + + + + + + + + +