fix: 修复前端页面的一些bug

This commit is contained in:
yarnom 2025-09-01 19:45:55 +08:00
parent 0a5a6ec4e2
commit 87ff8f44d7
4 changed files with 90 additions and 5 deletions

View File

@ -38,6 +38,10 @@ func main() {
var historicalEnd = flag.String("historical_end", "", "历史数据结束日期格式YYYY-MM-DD")
// 覆盖风:使用彩云实况替换导出中的风速/风向
var useWindOverride = flag.Bool("wind", false, "使用彩云实况覆盖导出CSV中的风速/风向")
// 历史CSV导出
var exportRangeOnly = flag.Bool("export_range", false, "按日期范围导出10分钟CSV含ZTD融合并退出。日期格式支持 YYYY-MM-DD 或 YYYYMMDD")
var exportStart = flag.String("export_start", "", "导出起始日期(含),格式 YYYY-MM-DD 或 YYYYMMDD")
var exportEnd = flag.String("export_end", "", "导出结束日期(含),格式 YYYY-MM-DD 或 YYYYMMDD")
flag.Parse()
// 设置日志
@ -87,6 +91,32 @@ func main() {
return
}
// 历史CSV范围导出
if *exportRangeOnly {
if *exportStart == "" || *exportEnd == "" {
log.Fatalln("export_range 需要提供 --export_start 与 --export_end 日期YYYY-MM-DD 或 YYYYMMDD")
}
var opts tools.ExporterOptions
if *useWindOverride {
token := os.Getenv("CAIYUN_TOKEN")
if token == "" {
token = config.GetConfig().Forecast.CaiyunToken
}
if token == "" {
log.Println("警告: 指定了 --wind 但未提供彩云 token忽略风覆盖")
} else {
opts.OverrideWindWithCaiyun = true
opts.CaiyunToken = token
}
}
exporter := tools.NewExporterWithOptions(opts)
if err := exporter.ExportRange(context.Background(), *exportStart, *exportEnd); err != nil {
log.Fatalf("export_range 失败: %v", err)
}
log.Println("export_range 完成")
return
}
// 工具按日期抓取当天0点到当前时间+3小时两家
if *forecastDay != "" {
if err := tools.RunForecastFetchForDay(context.Background(), *forecastDay); err != nil {

View File

@ -317,6 +317,56 @@ func (e *Exporter) exportBucket(ctx context.Context, bucketStart, bucketEnd time
return nil
}
// 导出一个日期范围内的全部10分钟桶CSTstart 与 end 为“日期起止(含)”
func (e *Exporter) ExportRange(ctx context.Context, startDate, endDate string) error {
// 解析日期,支持 YYYY-MM-DD 或 YYYYMMDD
parse := func(s string) (time.Time, error) {
if len(s) == 8 {
// YYYYMMDD -> YYYY-MM-DD
s = s[:4] + "-" + s[4:6] + "-" + s[6:8]
}
loc := e.loc
if loc == nil {
loc = time.FixedZone("CST", 8*3600)
}
return time.ParseInLocation("2006-01-02", s, loc)
}
fromDay, err := parse(startDate)
if err != nil {
return fmt.Errorf("起始日期解析失败: %v", err)
}
toDay, err := parse(endDate)
if err != nil {
return fmt.Errorf("结束日期解析失败: %v", err)
}
if toDay.Before(fromDay) {
return fmt.Errorf("结束日期早于起始日期")
}
// 日期起止 -> 时间范围(含):[fromDay 00:00, toDay 23:59:59]
from := time.Date(fromDay.Year(), fromDay.Month(), fromDay.Day(), 0, 0, 0, 0, e.loc)
to := time.Date(toDay.Year(), toDay.Month(), toDay.Day(), 23, 59, 59, 0, e.loc)
firstBucket := from.Truncate(10 * time.Minute)
lastBucket := to.Truncate(10 * time.Minute)
for b := firstBucket; !b.After(lastBucket); b = b.Add(10 * time.Minute) {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
bucketStart := b
bucketEnd := b.Add(10 * time.Minute)
if err := e.exportBucket(ctx, bucketStart, bucketEnd); err != nil {
// 不中断整个范围导出,记录错误继续
e.logger.Printf("范围导出: 桶 %s-%s 导出失败: %v", bucketStart.Format("2006-01-02 15:04:05"), bucketEnd.Format("2006-01-02 15:04:05"), err)
}
}
return nil
}
func (e *Exporter) lookupZTD(ctx context.Context, deviceID string, bucketEnd time.Time) string {
if e.my == nil {
return ""

View File

@ -8,13 +8,18 @@ const WeatherTable = {
}
const showPastEl = document.getElementById('showPastForecast');
const nowTs = Date.now();
const future3hTs = nowTs + 3 * 60 * 60 * 1000;
const endInput = document.getElementById('endDate')?.value;
let endTs = Date.now();
if (endInput) {
const d = new Date(endInput);
if (!isNaN(d)) endTs = d.getTime();
}
const future3hTs = endTs + 3 * 60 * 60 * 1000;
const showPast = !!(showPastEl && showPastEl.checked);
const displayedForecast = forecastData.filter(item => {
const t = new Date(item.date_time).getTime();
const isFuture3h = t > nowTs && t <= future3hTs;
const isPast = t <= nowTs;
const isFuture3h = t > endTs && t <= future3hTs;
const isPast = t <= endTs;
return isFuture3h || (showPast && isPast);
});
const taggedHistory = historyData.map(item => ({ ...item, __source: '实测' }));

View File

@ -533,7 +533,7 @@
</div>
<div class="table-container" id="tableContainer">
<div id="forecastToggleContainer" style="padding: 8px 12px; font-size: 12px; color: #666; display: none; display: flex; justify-content: center; align-items: center;">
<div id="forecastToggleContainer" style="padding: 8px 12px;font-size: 12px;color: #666;display: none;display: flex;justify-content: flex-start;align-items: center;align-content: center;">
<label style="display: flex; align-items: flex-start; gap: 5px;">
<input type="checkbox" id="showPastForecast" style="vertical-align: middle;" x-model="showPastForecast" @change="window.WeatherTable.display(window.WeatherApp.cachedHistoryData, window.WeatherApp.cachedForecastData)">
显示历史预报