4 Комити 364730101e ... 4c0cc8a95c

Аутор SHA1 Порука Датум
  taosj 4c0cc8a95c [10023] 气压曲线页面优化: psfcchart接口改为返回forecast/local时间点并集(时间升序, 缺数填0, 两侧stats等长下标对齐, 预测源同时间点多份重叠报告取首个即最新); 明细表与导出Excel均改为 时间/预测气压(hPa)/观测气压(hPa) 3列, 导出新增PsfcChartExportStat VO пре 1 недеља
  taosj 6b2e38d0cd [10023] 湿度曲线页面优化: rhsfcchart接口改为返回forecast/local时间点并集(时间升序, 缺数填0, 两侧stats等长下标对齐, 预测源同时间点多份重叠报告取首个即最新); 明细表与导出Excel均改为 时间/预测湿度(%)/观测湿度(%) 3列, 导出新增RhsfcChartExportStat VO пре 1 недеља
  taosj b7acdca834 [10023] 风速曲线页面优化: wschart接口改为返回forecast/local时间点并集(时间升序, 缺数填0, 两侧stats等长下标对齐, 预测源同时间点多份重叠报告取首个即最新); 明细表与导出Excel均改为 时间/预测风速(m/s)/观测风速(m/s) 3列, 导出新增WsChartExportStat VO пре 1 недеља
  taosj 1c5fe1162d [10023] 温度曲线页面优化: tsfcchart接口改为返回forecast/local时间点并集(时间升序, 缺数填0, 两侧stats等长下标对齐, 预测源同时间点多份重叠报告取首个即最新); 明细表与导出Excel均改为 时间/预测温度(°C)/观测温度(°C) 3列, 导出新增TsfcChartExportStat VO пре 1 недеља

+ 232 - 36
forecast-backend-server/src/main/java/com/ufo/project/forecast/shortterm/weather/controller/DataShortTermWeatherForecastRawController.java

@@ -21,8 +21,10 @@ import com.ufo.base.framework.aspectj.lang.annotation.Log;
 import com.ufo.base.framework.aspectj.lang.enums.BusinessType;
 import com.ufo.project.forecast.shortterm.weather.domain.DataShortTermWeatherForecastRaw;
 import com.ufo.project.forecast.shortterm.weather.domain.vo.ChartDataPair;
+import com.ufo.project.forecast.shortterm.weather.domain.vo.PsfcChartExportStat;
 import com.ufo.project.forecast.shortterm.weather.domain.vo.PsfcChartResult;
 import com.ufo.project.forecast.shortterm.weather.domain.vo.PsfcChartStat;
+import com.ufo.project.forecast.shortterm.weather.domain.vo.RhsfcChartExportStat;
 import com.ufo.project.forecast.shortterm.weather.domain.vo.RhsfcChartResult;
 import com.ufo.project.forecast.shortterm.weather.domain.vo.RhsfcChartStat;
 import com.ufo.project.forecast.shortterm.weather.domain.vo.RoseChartDirectionStat;
@@ -30,8 +32,10 @@ import com.ufo.project.forecast.shortterm.weather.domain.vo.RoseChartResult;
 import com.ufo.project.forecast.shortterm.weather.domain.vo.TirrChartExportStat;
 import com.ufo.project.forecast.shortterm.weather.domain.vo.TirrChartResult;
 import com.ufo.project.forecast.shortterm.weather.domain.vo.TirrChartStat;
+import com.ufo.project.forecast.shortterm.weather.domain.vo.TsfcChartExportStat;
 import com.ufo.project.forecast.shortterm.weather.domain.vo.TsfcChartResult;
 import com.ufo.project.forecast.shortterm.weather.domain.vo.TsfcChartStat;
+import com.ufo.project.forecast.shortterm.weather.domain.vo.WsChartExportStat;
 import com.ufo.project.forecast.shortterm.weather.domain.vo.WsChartResult;
 import com.ufo.project.forecast.shortterm.weather.domain.vo.WsChartStat;
 import com.ufo.project.forecast.shortterm.weather.service.IDataShortTermWeatherForecastRawService;
@@ -95,7 +99,7 @@ public class DataShortTermWeatherForecastRawController extends BaseController
     }
 
     /**
-     * 统计风速曲线数据(短期预报 + 本地实测)
+     * 统计风速曲线数据(短期预报 + 本地实测,两侧 stats 对齐到同一时间点并集,缺数填0)
      */
     @PreAuthorize("@ss.hasPermi('weather:weatherRaw:wschart')")
     @GetMapping("/wschart")
@@ -104,11 +108,12 @@ public class DataShortTermWeatherForecastRawController extends BaseController
     {
         WsChartResult forecast = dataShortTermWeatherForecastRawService.getWsChart(stationId, startTime, endTime);
         WsChartResult local = dataWeatherLocalService.getWsChart(stationId, startTime, endTime);
+        alignWsStats(forecast, local);
         return success(new ChartDataPair<WsChartResult>(forecast, local));
     }
 
     /**
-     * 导出风速曲线数据(短期预报 + 本地实测,合并导出)
+     * 导出风速曲线数据(短期预报 + 本地实测,按时间点并集对齐导出 时间/预测风速/观测风速)
      */
     @PreAuthorize("@ss.hasPermi('weather:weatherRaw:wschart')")
     @Log(title = "风速曲线", businessType = BusinessType.EXPORT)
@@ -118,21 +123,21 @@ public class DataShortTermWeatherForecastRawController extends BaseController
     {
         WsChartResult forecast = dataShortTermWeatherForecastRawService.getWsChart(stationId, startTime, endTime);
         WsChartResult local = dataWeatherLocalService.getWsChart(stationId, startTime, endTime);
-        List<WsChartStat> stats = new ArrayList<WsChartStat>();
-        if (forecast.getStats() != null)
-        {
-            stats.addAll(forecast.getStats());
-        }
-        if (local.getStats() != null)
+        alignWsStats(forecast, local);
+        List<WsChartStat> forecastStats = forecast.getStats();
+        List<WsChartStat> localStats = local.getStats();
+        List<WsChartExportStat> stats = new ArrayList<WsChartExportStat>();
+        for (int i = 0; i < forecastStats.size(); i++)
         {
-            stats.addAll(local.getStats());
+            stats.add(new WsChartExportStat(forecastStats.get(i).getTime(),
+                    forecastStats.get(i).getWs(), localStats.get(i).getWs()));
         }
-        ExcelUtil<WsChartStat> util = new ExcelUtil<WsChartStat>(WsChartStat.class);
+        ExcelUtil<WsChartExportStat> util = new ExcelUtil<WsChartExportStat>(WsChartExportStat.class);
         util.exportExcel(response, stats, "风速曲线数据");
     }
 
     /**
-     * 统计温度曲线数据(短期预报 + 本地实测)
+     * 统计温度曲线数据(短期预报 + 本地实测,两侧 stats 对齐到同一时间点并集,缺数填0)
      */
     @PreAuthorize("@ss.hasPermi('weather:weatherRaw:tsfcchart')")
     @GetMapping("/tsfcchart")
@@ -141,11 +146,12 @@ public class DataShortTermWeatherForecastRawController extends BaseController
     {
         TsfcChartResult forecast = dataShortTermWeatherForecastRawService.getTsfcChart(stationId, startTime, endTime);
         TsfcChartResult local = dataWeatherLocalService.getTsfcChart(stationId, startTime, endTime);
+        alignTsfcStats(forecast, local);
         return success(new ChartDataPair<TsfcChartResult>(forecast, local));
     }
 
     /**
-     * 导出温度曲线数据(短期预报 + 本地实测,合并导出)
+     * 导出温度曲线数据(短期预报 + 本地实测,按时间点并集对齐导出 时间/预测温度/观测温度)
      */
     @PreAuthorize("@ss.hasPermi('weather:weatherRaw:tsfcchart')")
     @Log(title = "温度曲线", businessType = BusinessType.EXPORT)
@@ -155,16 +161,16 @@ public class DataShortTermWeatherForecastRawController extends BaseController
     {
         TsfcChartResult forecast = dataShortTermWeatherForecastRawService.getTsfcChart(stationId, startTime, endTime);
         TsfcChartResult local = dataWeatherLocalService.getTsfcChart(stationId, startTime, endTime);
-        List<TsfcChartStat> stats = new ArrayList<TsfcChartStat>();
-        if (forecast.getStats() != null)
-        {
-            stats.addAll(forecast.getStats());
-        }
-        if (local.getStats() != null)
+        alignTsfcStats(forecast, local);
+        List<TsfcChartStat> forecastStats = forecast.getStats();
+        List<TsfcChartStat> localStats = local.getStats();
+        List<TsfcChartExportStat> stats = new ArrayList<TsfcChartExportStat>();
+        for (int i = 0; i < forecastStats.size(); i++)
         {
-            stats.addAll(local.getStats());
+            stats.add(new TsfcChartExportStat(forecastStats.get(i).getTime(),
+                    forecastStats.get(i).getTsfc(), localStats.get(i).getTsfc()));
         }
-        ExcelUtil<TsfcChartStat> util = new ExcelUtil<TsfcChartStat>(TsfcChartStat.class);
+        ExcelUtil<TsfcChartExportStat> util = new ExcelUtil<TsfcChartExportStat>(TsfcChartExportStat.class);
         util.exportExcel(response, stats, "温度曲线数据");
     }
 
@@ -178,6 +184,7 @@ public class DataShortTermWeatherForecastRawController extends BaseController
     {
         RhsfcChartResult forecast = dataShortTermWeatherForecastRawService.getRhsfcChart(stationId, startTime, endTime);
         RhsfcChartResult local = dataWeatherLocalService.getRhsfcChart(stationId, startTime, endTime);
+        alignRhsfcStats(forecast, local);
         return success(new ChartDataPair<RhsfcChartResult>(forecast, local));
     }
 
@@ -192,16 +199,16 @@ public class DataShortTermWeatherForecastRawController extends BaseController
     {
         RhsfcChartResult forecast = dataShortTermWeatherForecastRawService.getRhsfcChart(stationId, startTime, endTime);
         RhsfcChartResult local = dataWeatherLocalService.getRhsfcChart(stationId, startTime, endTime);
-        List<RhsfcChartStat> stats = new ArrayList<RhsfcChartStat>();
-        if (forecast.getStats() != null)
-        {
-            stats.addAll(forecast.getStats());
-        }
-        if (local.getStats() != null)
+        alignRhsfcStats(forecast, local);
+        List<RhsfcChartStat> forecastStats = forecast.getStats();
+        List<RhsfcChartStat> localStats = local.getStats();
+        List<RhsfcChartExportStat> stats = new ArrayList<RhsfcChartExportStat>();
+        for (int i = 0; i < forecastStats.size(); i++)
         {
-            stats.addAll(local.getStats());
+            stats.add(new RhsfcChartExportStat(forecastStats.get(i).getTime(),
+                    forecastStats.get(i).getRhsfc(), localStats.get(i).getRhsfc()));
         }
-        ExcelUtil<RhsfcChartStat> util = new ExcelUtil<RhsfcChartStat>(RhsfcChartStat.class);
+        ExcelUtil<RhsfcChartExportStat> util = new ExcelUtil<RhsfcChartExportStat>(RhsfcChartExportStat.class);
         util.exportExcel(response, stats, "湿度曲线数据");
     }
 
@@ -215,6 +222,7 @@ public class DataShortTermWeatherForecastRawController extends BaseController
     {
         PsfcChartResult forecast = dataShortTermWeatherForecastRawService.getPsfcChart(stationId, startTime, endTime);
         PsfcChartResult local = dataWeatherLocalService.getPsfcChart(stationId, startTime, endTime);
+        alignPsfcStats(forecast, local);
         return success(new ChartDataPair<PsfcChartResult>(forecast, local));
     }
 
@@ -229,16 +237,16 @@ public class DataShortTermWeatherForecastRawController extends BaseController
     {
         PsfcChartResult forecast = dataShortTermWeatherForecastRawService.getPsfcChart(stationId, startTime, endTime);
         PsfcChartResult local = dataWeatherLocalService.getPsfcChart(stationId, startTime, endTime);
-        List<PsfcChartStat> stats = new ArrayList<PsfcChartStat>();
-        if (forecast.getStats() != null)
-        {
-            stats.addAll(forecast.getStats());
-        }
-        if (local.getStats() != null)
+        alignPsfcStats(forecast, local);
+        List<PsfcChartStat> forecastStats = forecast.getStats();
+        List<PsfcChartStat> localStats = local.getStats();
+        List<PsfcChartExportStat> stats = new ArrayList<PsfcChartExportStat>();
+        for (int i = 0; i < forecastStats.size(); i++)
         {
-            stats.addAll(local.getStats());
+            stats.add(new PsfcChartExportStat(forecastStats.get(i).getTime(),
+                    forecastStats.get(i).getPsfc(), localStats.get(i).getPsfc()));
         }
-        ExcelUtil<PsfcChartStat> util = new ExcelUtil<PsfcChartStat>(PsfcChartStat.class);
+        ExcelUtil<PsfcChartExportStat> util = new ExcelUtil<PsfcChartExportStat>(PsfcChartExportStat.class);
         util.exportExcel(response, stats, "气压曲线数据");
     }
 
@@ -328,6 +336,194 @@ public class DataShortTermWeatherForecastRawController extends BaseController
         return map;
     }
 
+    /**
+     * 将温度曲线的短期预报与本地实测对齐到同一时间点并集(时间从小到大)。
+     * 某侧缺失的时间点 tsfc 填 0;预测源同一时间点可能含多份重叠报告,保留列表中首个(最新报告)。
+     * 合并后两侧 stats 等长、下标一一对应。
+     */
+    private void alignTsfcStats(TsfcChartResult forecast, TsfcChartResult local)
+    {
+        Map<String, Double> forecastMap = buildTsfcTimeMap(forecast.getStats());
+        Map<String, Double> localMap = buildTsfcTimeMap(local.getStats());
+        TreeSet<String> times = new TreeSet<String>();
+        times.addAll(forecastMap.keySet());
+        times.addAll(localMap.keySet());
+        List<TsfcChartStat> forecastStats = new ArrayList<TsfcChartStat>();
+        List<TsfcChartStat> localStats = new ArrayList<TsfcChartStat>();
+        for (String time : times)
+        {
+            TsfcChartStat forecastStat = new TsfcChartStat(time, forecastMap.getOrDefault(time, 0d));
+            forecastStat.setSource("预测");
+            forecastStats.add(forecastStat);
+            TsfcChartStat localStat = new TsfcChartStat(time, localMap.getOrDefault(time, 0d));
+            localStat.setSource("本地实测");
+            localStats.add(localStat);
+        }
+        forecast.setStats(forecastStats);
+        local.setStats(localStats);
+    }
+
+    /**
+     * 按时间建立 时间->温度 映射(LinkedHashMap 保留列表序,putIfAbsent 去重取首个)
+     */
+    private Map<String, Double> buildTsfcTimeMap(List<TsfcChartStat> stats)
+    {
+        Map<String, Double> map = new LinkedHashMap<String, Double>();
+        if (stats == null)
+        {
+            return map;
+        }
+        for (TsfcChartStat stat : stats)
+        {
+            if (stat == null || stat.getTime() == null)
+            {
+                continue;
+            }
+            map.putIfAbsent(stat.getTime(), stat.getTsfc() == null ? 0d : stat.getTsfc());
+        }
+        return map;
+    }
+
+    /**
+     * 将风速曲线的短期预报与本地实测对齐到同一时间点并集(时间从小到大)。
+     * 某侧缺失的时间点 ws 填 0;预测源同一时间点可能含多份重叠报告,保留列表中首个(最新报告)。
+     * 合并后两侧 stats 等长、下标一一对应。
+     */
+    private void alignWsStats(WsChartResult forecast, WsChartResult local)
+    {
+        Map<String, Double> forecastMap = buildWsTimeMap(forecast.getStats());
+        Map<String, Double> localMap = buildWsTimeMap(local.getStats());
+        TreeSet<String> times = new TreeSet<String>();
+        times.addAll(forecastMap.keySet());
+        times.addAll(localMap.keySet());
+        List<WsChartStat> forecastStats = new ArrayList<WsChartStat>();
+        List<WsChartStat> localStats = new ArrayList<WsChartStat>();
+        for (String time : times)
+        {
+            WsChartStat forecastStat = new WsChartStat(time, forecastMap.getOrDefault(time, 0d));
+            forecastStat.setSource("预测");
+            forecastStats.add(forecastStat);
+            WsChartStat localStat = new WsChartStat(time, localMap.getOrDefault(time, 0d));
+            localStat.setSource("本地实测");
+            localStats.add(localStat);
+        }
+        forecast.setStats(forecastStats);
+        local.setStats(localStats);
+    }
+
+    /**
+     * 按时间建立 时间->风速 映射(LinkedHashMap 保留列表序,putIfAbsent 去重取首个)
+     */
+    private Map<String, Double> buildWsTimeMap(List<WsChartStat> stats)
+    {
+        Map<String, Double> map = new LinkedHashMap<String, Double>();
+        if (stats == null)
+        {
+            return map;
+        }
+        for (WsChartStat stat : stats)
+        {
+            if (stat == null || stat.getTime() == null)
+            {
+                continue;
+            }
+            map.putIfAbsent(stat.getTime(), stat.getWs() == null ? 0d : stat.getWs());
+        }
+        return map;
+    }
+
+    /**
+     * 将预测/本地实测湿度统计对齐到同一时间点并集(升序,缺数填0,等长下标一一对应)
+     */
+    private void alignRhsfcStats(RhsfcChartResult forecast, RhsfcChartResult local)
+    {
+        Map<String, Double> forecastMap = buildRhsfcTimeMap(forecast.getStats());
+        Map<String, Double> localMap = buildRhsfcTimeMap(local.getStats());
+        TreeSet<String> times = new TreeSet<String>();
+        times.addAll(forecastMap.keySet());
+        times.addAll(localMap.keySet());
+        List<RhsfcChartStat> forecastStats = new ArrayList<RhsfcChartStat>();
+        List<RhsfcChartStat> localStats = new ArrayList<RhsfcChartStat>();
+        for (String time : times)
+        {
+            RhsfcChartStat forecastStat = new RhsfcChartStat(time, forecastMap.getOrDefault(time, 0d));
+            forecastStat.setSource("预测");
+            forecastStats.add(forecastStat);
+            RhsfcChartStat localStat = new RhsfcChartStat(time, localMap.getOrDefault(time, 0d));
+            localStat.setSource("本地实测");
+            localStats.add(localStat);
+        }
+        forecast.setStats(forecastStats);
+        local.setStats(localStats);
+    }
+
+    /**
+     * 按时间建立 时间->湿度 映射(LinkedHashMap 保留列表序,putIfAbsent 去重取首个)
+     */
+    private Map<String, Double> buildRhsfcTimeMap(List<RhsfcChartStat> stats)
+    {
+        Map<String, Double> map = new LinkedHashMap<String, Double>();
+        if (stats == null)
+        {
+            return map;
+        }
+        for (RhsfcChartStat stat : stats)
+        {
+            if (stat == null || stat.getTime() == null)
+            {
+                continue;
+            }
+            map.putIfAbsent(stat.getTime(), stat.getRhsfc() == null ? 0d : stat.getRhsfc());
+        }
+        return map;
+    }
+
+    /**
+     * 将预测/本地实测气压统计对齐到同一时间点并集(升序,缺数填0,等长下标一一对应)
+     */
+    private void alignPsfcStats(PsfcChartResult forecast, PsfcChartResult local)
+    {
+        Map<String, Double> forecastMap = buildPsfcTimeMap(forecast.getStats());
+        Map<String, Double> localMap = buildPsfcTimeMap(local.getStats());
+        TreeSet<String> times = new TreeSet<String>();
+        times.addAll(forecastMap.keySet());
+        times.addAll(localMap.keySet());
+        List<PsfcChartStat> forecastStats = new ArrayList<PsfcChartStat>();
+        List<PsfcChartStat> localStats = new ArrayList<PsfcChartStat>();
+        for (String time : times)
+        {
+            PsfcChartStat forecastStat = new PsfcChartStat(time, forecastMap.getOrDefault(time, 0d));
+            forecastStat.setSource("预测");
+            forecastStats.add(forecastStat);
+            PsfcChartStat localStat = new PsfcChartStat(time, localMap.getOrDefault(time, 0d));
+            localStat.setSource("本地实测");
+            localStats.add(localStat);
+        }
+        forecast.setStats(forecastStats);
+        local.setStats(localStats);
+    }
+
+    /**
+     * 按时间建立 时间->气压 映射(LinkedHashMap 保留列表序,putIfAbsent 去重取首个)
+     */
+    private Map<String, Double> buildPsfcTimeMap(List<PsfcChartStat> stats)
+    {
+        Map<String, Double> map = new LinkedHashMap<String, Double>();
+        if (stats == null)
+        {
+            return map;
+        }
+        for (PsfcChartStat stat : stats)
+        {
+            if (stat == null || stat.getTime() == null)
+            {
+                continue;
+            }
+            map.putIfAbsent(stat.getTime(), stat.getPsfc() == null ? 0d : stat.getPsfc());
+        }
+        return map;
+    }
+
     /**
      * 导出短期气象原始数据列表
      */

+ 65 - 0
forecast-backend-server/src/main/java/com/ufo/project/forecast/shortterm/weather/domain/vo/PsfcChartExportStat.java

@@ -0,0 +1,65 @@
+package com.ufo.project.forecast.shortterm.weather.domain.vo;
+
+import com.ufo.base.framework.aspectj.lang.annotation.Excel;
+
+/**
+ * 气压曲线导出行数据(预测与本地实测按时间点并集对齐,缺数填0)
+ *
+ * @author ufo
+ * @date 2026-10-29
+ */
+public class PsfcChartExportStat
+{
+    /** 时间(yyyy-MM-dd HH:mm:ss) */
+    @Excel(name = "时间")
+    private String time;
+
+    /** 预测气压(hPa) */
+    @Excel(name = "预测气压(hPa)")
+    private Double forecastPsfc;
+
+    /** 观测气压(hPa) */
+    @Excel(name = "观测气压(hPa)")
+    private Double localPsfc;
+
+    public PsfcChartExportStat()
+    {
+    }
+
+    public PsfcChartExportStat(String time, Double forecastPsfc, Double localPsfc)
+    {
+        this.time = time;
+        this.forecastPsfc = forecastPsfc;
+        this.localPsfc = localPsfc;
+    }
+
+    public String getTime()
+    {
+        return time;
+    }
+
+    public void setTime(String time)
+    {
+        this.time = time;
+    }
+
+    public Double getForecastPsfc()
+    {
+        return forecastPsfc;
+    }
+
+    public void setForecastPsfc(Double forecastPsfc)
+    {
+        this.forecastPsfc = forecastPsfc;
+    }
+
+    public Double getLocalPsfc()
+    {
+        return localPsfc;
+    }
+
+    public void setLocalPsfc(Double localPsfc)
+    {
+        this.localPsfc = localPsfc;
+    }
+}

+ 65 - 0
forecast-backend-server/src/main/java/com/ufo/project/forecast/shortterm/weather/domain/vo/RhsfcChartExportStat.java

@@ -0,0 +1,65 @@
+package com.ufo.project.forecast.shortterm.weather.domain.vo;
+
+import com.ufo.base.framework.aspectj.lang.annotation.Excel;
+
+/**
+ * 湿度曲线导出行数据(预测与本地实测按时间点并集对齐,缺数填0)
+ *
+ * @author ufo
+ * @date 2026-10-29
+ */
+public class RhsfcChartExportStat
+{
+    /** 时间(yyyy-MM-dd HH:mm:ss) */
+    @Excel(name = "时间")
+    private String time;
+
+    /** 预测湿度(%) */
+    @Excel(name = "预测湿度(%)")
+    private Double forecastRhsfc;
+
+    /** 观测湿度(%) */
+    @Excel(name = "观测湿度(%)")
+    private Double localRhsfc;
+
+    public RhsfcChartExportStat()
+    {
+    }
+
+    public RhsfcChartExportStat(String time, Double forecastRhsfc, Double localRhsfc)
+    {
+        this.time = time;
+        this.forecastRhsfc = forecastRhsfc;
+        this.localRhsfc = localRhsfc;
+    }
+
+    public String getTime()
+    {
+        return time;
+    }
+
+    public void setTime(String time)
+    {
+        this.time = time;
+    }
+
+    public Double getForecastRhsfc()
+    {
+        return forecastRhsfc;
+    }
+
+    public void setForecastRhsfc(Double forecastRhsfc)
+    {
+        this.forecastRhsfc = forecastRhsfc;
+    }
+
+    public Double getLocalRhsfc()
+    {
+        return localRhsfc;
+    }
+
+    public void setLocalRhsfc(Double localRhsfc)
+    {
+        this.localRhsfc = localRhsfc;
+    }
+}

+ 65 - 0
forecast-backend-server/src/main/java/com/ufo/project/forecast/shortterm/weather/domain/vo/TsfcChartExportStat.java

@@ -0,0 +1,65 @@
+package com.ufo.project.forecast.shortterm.weather.domain.vo;
+
+import com.ufo.base.framework.aspectj.lang.annotation.Excel;
+
+/**
+ * 温度曲线导出行数据(预测与本地实测按时间点并集对齐,缺数填0)
+ *
+ * @author ufo
+ * @date 2026-10-29
+ */
+public class TsfcChartExportStat
+{
+    /** 时间(yyyy-MM-dd HH:mm:ss) */
+    @Excel(name = "时间")
+    private String time;
+
+    /** 预测温度(°C) */
+    @Excel(name = "预测温度(°C)")
+    private Double forecastTsfc;
+
+    /** 观测温度(°C) */
+    @Excel(name = "观测温度(°C)")
+    private Double localTsfc;
+
+    public TsfcChartExportStat()
+    {
+    }
+
+    public TsfcChartExportStat(String time, Double forecastTsfc, Double localTsfc)
+    {
+        this.time = time;
+        this.forecastTsfc = forecastTsfc;
+        this.localTsfc = localTsfc;
+    }
+
+    public String getTime()
+    {
+        return time;
+    }
+
+    public void setTime(String time)
+    {
+        this.time = time;
+    }
+
+    public Double getForecastTsfc()
+    {
+        return forecastTsfc;
+    }
+
+    public void setForecastTsfc(Double forecastTsfc)
+    {
+        this.forecastTsfc = forecastTsfc;
+    }
+
+    public Double getLocalTsfc()
+    {
+        return localTsfc;
+    }
+
+    public void setLocalTsfc(Double localTsfc)
+    {
+        this.localTsfc = localTsfc;
+    }
+}

+ 65 - 0
forecast-backend-server/src/main/java/com/ufo/project/forecast/shortterm/weather/domain/vo/WsChartExportStat.java

@@ -0,0 +1,65 @@
+package com.ufo.project.forecast.shortterm.weather.domain.vo;
+
+import com.ufo.base.framework.aspectj.lang.annotation.Excel;
+
+/**
+ * 风速曲线导出行数据(预测与本地实测按时间点并集对齐,缺数填0)
+ *
+ * @author ufo
+ * @date 2026-10-29
+ */
+public class WsChartExportStat
+{
+    /** 时间(yyyy-MM-dd HH:mm:ss) */
+    @Excel(name = "时间")
+    private String time;
+
+    /** 预测风速(m/s) */
+    @Excel(name = "预测风速(m/s)")
+    private Double forecastWs;
+
+    /** 观测风速(m/s) */
+    @Excel(name = "观测风速(m/s)")
+    private Double localWs;
+
+    public WsChartExportStat()
+    {
+    }
+
+    public WsChartExportStat(String time, Double forecastWs, Double localWs)
+    {
+        this.time = time;
+        this.forecastWs = forecastWs;
+        this.localWs = localWs;
+    }
+
+    public String getTime()
+    {
+        return time;
+    }
+
+    public void setTime(String time)
+    {
+        this.time = time;
+    }
+
+    public Double getForecastWs()
+    {
+        return forecastWs;
+    }
+
+    public void setForecastWs(Double forecastWs)
+    {
+        this.forecastWs = forecastWs;
+    }
+
+    public Double getLocalWs()
+    {
+        return localWs;
+    }
+
+    public void setLocalWs(Double localWs)
+    {
+        this.localWs = localWs;
+    }
+}

+ 12 - 16
forecast-backend-vue/src/views/ufo/electricity/weather/HumidityCurve.vue

@@ -84,8 +84,8 @@ const hasData = computed(() => !!summary.value && (summary.value.totalRecords >
 
 const columns = [
   {title: '时间', dataIndex: 'time', width: 180, align: 'center', fixed: 'left'},
-  {title: '来源', dataIndex: 'source', width: 100, align: 'center'},
-  {title: '湿度(%)', dataIndex: 'rhsfc', width: 150, align: 'right'}
+  {title: '预测湿度(%)', dataIndex: 'forecastRhsfc', width: 150, align: 'right'},
+  {title: '观测湿度(%)', dataIndex: 'localRhsfc', width: 150, align: 'right'}
 ];
 
 const pagination = {
@@ -191,20 +191,16 @@ const handleQuery = async () => {
       localTotalRecords: local.totalRecords || 0,
       localValidRecords: local.validRecords || 0
     };
-    detailRows.value = [
-      ...(forecast.stats || []).map((item, index) => ({
-        key: `forecast-${index}`,
-        time: item.time,
-        source: item.source || '预测',
-        rhsfc: toNumber(item.rhsfc, 0)
-      })),
-      ...(local.stats || []).map((item, index) => ({
-        key: `local-${index}`,
-        time: item.time,
-        source: item.source || '观测',
-        rhsfc: toNumber(item.rhsfc, 0)
-      }))
-    ];
+    // 后端已将 forecast/local 的 stats 对齐到同一时间点并集(缺数填0),按下标对齐取行
+    const forecastStats = forecast.stats || [];
+    const localStats = local.stats || [];
+    const rowCount = Math.max(forecastStats.length, localStats.length);
+    detailRows.value = Array.from({ length: rowCount }, (_, index) => ({
+      key: `row-${index}`,
+      time: forecastStats[index]?.time || localStats[index]?.time,
+      forecastRhsfc: toNumber(forecastStats[index]?.rhsfc, 0),
+      localRhsfc: toNumber(localStats[index]?.rhsfc, 0)
+    }));
     renderChart(forecast.stats || [], local.stats || []);
   } finally {
     loading.value = false;

+ 12 - 16
forecast-backend-vue/src/views/ufo/electricity/weather/PressureCurve.vue

@@ -84,8 +84,8 @@ const hasData = computed(() => !!summary.value && (summary.value.totalRecords >
 
 const columns = [
   {title: '时间', dataIndex: 'time', width: 180, align: 'center', fixed: 'left'},
-  {title: '来源', dataIndex: 'source', width: 100, align: 'center'},
-  {title: '气压(hPa)', dataIndex: 'psfc', width: 150, align: 'right'}
+  {title: '预测气压(hPa)', dataIndex: 'forecastPsfc', width: 150, align: 'right'},
+  {title: '观测气压(hPa)', dataIndex: 'localPsfc', width: 150, align: 'right'}
 ];
 
 const pagination = {
@@ -191,20 +191,16 @@ const handleQuery = async () => {
       localTotalRecords: local.totalRecords || 0,
       localValidRecords: local.validRecords || 0
     };
-    detailRows.value = [
-      ...(forecast.stats || []).map((item, index) => ({
-        key: `forecast-${index}`,
-        time: item.time,
-        source: item.source || '预测',
-        psfc: toNumber(item.psfc, 0)
-      })),
-      ...(local.stats || []).map((item, index) => ({
-        key: `local-${index}`,
-        time: item.time,
-        source: item.source || '观测',
-        psfc: toNumber(item.psfc, 0)
-      }))
-    ];
+    // 后端已将 forecast/local 的 stats 对齐到同一时间点并集(缺数填0),按下标对齐取行
+    const forecastStats = forecast.stats || [];
+    const localStats = local.stats || [];
+    const rowCount = Math.max(forecastStats.length, localStats.length);
+    detailRows.value = Array.from({ length: rowCount }, (_, index) => ({
+      key: `row-${index}`,
+      time: forecastStats[index]?.time || localStats[index]?.time,
+      forecastPsfc: toNumber(forecastStats[index]?.psfc, 0),
+      localPsfc: toNumber(localStats[index]?.psfc, 0)
+    }));
     renderChart(forecast.stats || [], local.stats || []);
   } finally {
     loading.value = false;

+ 12 - 16
forecast-backend-vue/src/views/ufo/electricity/weather/TemperatureCurve.vue

@@ -84,8 +84,8 @@ const hasData = computed(() => !!summary.value && (summary.value.totalRecords >
 
 const columns = [
   {title: '时间', dataIndex: 'time', width: 180, align: 'center', fixed: 'left'},
-  {title: '来源', dataIndex: 'source', width: 100, align: 'center'},
-  {title: '温度(°C)', dataIndex: 'tsfc', width: 150, align: 'right'}
+  {title: '预测温度(°C)', dataIndex: 'forecastTsfc', width: 150, align: 'right'},
+  {title: '观测温度(°C)', dataIndex: 'localTsfc', width: 150, align: 'right'}
 ];
 
 const pagination = {
@@ -191,20 +191,16 @@ const handleQuery = async () => {
       localTotalRecords: local.totalRecords || 0,
       localValidRecords: local.validRecords || 0
     };
-    detailRows.value = [
-      ...(forecast.stats || []).map((item, index) => ({
-        key: `forecast-${index}`,
-        time: item.time,
-        source: item.source || '预测',
-        tsfc: toNumber(item.tsfc, 0)
-      })),
-      ...(local.stats || []).map((item, index) => ({
-        key: `local-${index}`,
-        time: item.time,
-        source: item.source || '观测',
-        tsfc: toNumber(item.tsfc, 0)
-      }))
-    ];
+    // 后端已将 forecast/local 的 stats 对齐到同一时间点并集(缺数填0),按下标对齐取行
+    const forecastStats = forecast.stats || [];
+    const localStats = local.stats || [];
+    const rowCount = Math.max(forecastStats.length, localStats.length);
+    detailRows.value = Array.from({ length: rowCount }, (_, index) => ({
+      key: `row-${index}`,
+      time: forecastStats[index]?.time || localStats[index]?.time,
+      forecastTsfc: toNumber(forecastStats[index]?.tsfc, 0),
+      localTsfc: toNumber(localStats[index]?.tsfc, 0)
+    }));
     renderChart(forecast.stats || [], local.stats || []);
   } finally {
     loading.value = false;

+ 12 - 16
forecast-backend-vue/src/views/ufo/electricity/weather/WindSpeedCurve.vue

@@ -84,8 +84,8 @@ const hasData = computed(() => !!summary.value && (summary.value.totalRecords >
 
 const columns = [
   {title: '时间', dataIndex: 'time', width: 180, align: 'center', fixed: 'left'},
-  {title: '来源', dataIndex: 'source', width: 100, align: 'center'},
-  {title: '风速(m/s)', dataIndex: 'ws', width: 150, align: 'right'}
+  {title: '预测风速(m/s)', dataIndex: 'forecastWs', width: 150, align: 'right'},
+  {title: '观测风速(m/s)', dataIndex: 'localWs', width: 150, align: 'right'}
 ];
 
 const pagination = {
@@ -191,20 +191,16 @@ const handleQuery = async () => {
       localTotalRecords: local.totalRecords || 0,
       localValidRecords: local.validRecords || 0
     };
-    detailRows.value = [
-      ...(forecast.stats || []).map((item, index) => ({
-        key: `forecast-${index}`,
-        time: item.time,
-        source: item.source || '预测',
-        ws: toNumber(item.ws, 0)
-      })),
-      ...(local.stats || []).map((item, index) => ({
-        key: `local-${index}`,
-        time: item.time,
-        source: item.source || '观测',
-        ws: toNumber(item.ws, 0)
-      }))
-    ];
+    // 后端已将 forecast/local 的 stats 对齐到同一时间点并集(缺数填0),按下标对齐取行
+    const forecastStats = forecast.stats || [];
+    const localStats = local.stats || [];
+    const rowCount = Math.max(forecastStats.length, localStats.length);
+    detailRows.value = Array.from({ length: rowCount }, (_, index) => ({
+      key: `row-${index}`,
+      time: forecastStats[index]?.time || localStats[index]?.time,
+      forecastWs: toNumber(forecastStats[index]?.ws, 0),
+      localWs: toNumber(localStats[index]?.ws, 0)
+    }));
     renderChart(forecast.stats || [], local.stats || []);
   } finally {
     loading.value = false;