using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LiveChartsCore;
using LiveChartsCore.Defaults;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Painting;
using SkiaSharp;
using Serilog;
using YZWater.Core.Models;
using YZWater.Core.Services;
namespace YZWater.Core.ViewModels;
///
/// 流量记录视图 ViewModel
///
public partial class ViewCViewModel : TranslatableViewModelBase
{
private bool _disposed;
// ═══ 图表数据 ═══
[ObservableProperty] private ISeries[] _inflowSeries;
[ObservableProperty] private ISeries[] _outflowSeries;
[ObservableProperty] private ISeries[] _tankLevelSeries;
[ObservableProperty] private ISeries[] _pumpFreqSeries;
[ObservableProperty] private Axis[] _xAxes;
[ObservableProperty] private Axis[] _yAxes;
[ObservableProperty] private Axis[] _tankYAxes;
[ObservableProperty] private Axis[] _pumpYAxes;
// ═══ 查询条件 ═══
[ObservableProperty] private DateTimeOffset? _startDate = DateTimeOffset.Now.AddDays(-1);
[ObservableProperty] private DateTimeOffset? _endDate = DateTimeOffset.Now;
[ObservableProperty] private List _flowRecords = new();
// ─── 翻译属性 ───
public string TitleText => T("FlowRecords");
public string SubtitleText => T("FlowHistory");
public string FromText => T("From");
public string ToText => T("To");
public string QueryText => T("Query");
public string ExportCsvText => T("ExportCsv");
public string PurgeOldText => T("PurgeOld");
public string InflowTrendText => T("InflowTrend");
public string OutflowTrendText => T("OutflowTrend");
public string TankLevelTrendText => T("TankLevelTrend");
public string PumpFreqTrendText => T("PumpFreqTrend");
public string QuickRangeText => T("QuickRange");
public string FlowTrendText => T("FlowTrend");
public string TimeHeaderText => T("TimeHeader");
public string InflowHeaderText => T("InflowHeader");
public string OutflowHeaderText => T("OutflowHeader");
public string TotalInHeaderText => T("TotalInHeader");
public string TotalOutHeaderText => T("TotalOutHeader");
public ViewCViewModel()
{
RegisterTranslation(nameof(TitleText), "FlowRecords");
RegisterTranslation(nameof(SubtitleText), "FlowHistory");
RegisterTranslation(nameof(FromText), "From");
RegisterTranslation(nameof(ToText), "To");
RegisterTranslation(nameof(QueryText), "Query");
RegisterTranslation(nameof(ExportCsvText), "ExportCsv");
RegisterTranslation(nameof(PurgeOldText), "PurgeOld");
RegisterTranslation(nameof(InflowTrendText), "InflowTrend");
RegisterTranslation(nameof(OutflowTrendText), "OutflowTrend");
RegisterTranslation(nameof(TankLevelTrendText), "TankLevelTrend");
RegisterTranslation(nameof(PumpFreqTrendText), "PumpFreqTrend");
RegisterTranslation(nameof(QuickRangeText), "QuickRange");
RegisterTranslation(nameof(FlowTrendText), "FlowTrend");
RegisterTranslation(nameof(TimeHeaderText), "TimeHeader");
RegisterTranslation(nameof(InflowHeaderText), "InflowHeader");
RegisterTranslation(nameof(OutflowHeaderText), "OutflowHeader");
RegisterTranslation(nameof(TotalInHeaderText), "TotalInHeader");
RegisterTranslation(nameof(TotalOutHeaderText), "TotalOutHeader");
SubscribeLanguageChanged();
InitializeCharts();
PlcPollingService.Instance.DataUpdated += OnDataUpdated;
_ = LoadFlowRecordsAsync();
}
private void OnDataUpdated(PlcDataModel data)
{
AddRealtimePoint(data);
}
///
/// 添加实时数据点
///
private void AddRealtimePoint(PlcDataModel data)
{
try
{
var now = DateTime.Now;
AddPointToSeries(InflowSeries, now, data.InflowRate);
AddPointToSeries(OutflowSeries, now, data.OutflowRate);
}
catch (Exception ex)
{
Log.Error(ex, "更新实时图表失败");
}
}
private static void AddPointToSeries(ISeries[] series, DateTime time, float value)
{
if (series?.Length > 0 && series[0] is LineSeries lineSeries)
{
var values = lineSeries.Values?.ToList() ?? new List();
values.Add(new DateTimePoint(time, value));
if (values.Count > 120) values.RemoveAt(0);
lineSeries.Values = values.ToArray();
}
}
///
/// 初始化所有图表
///
private void InitializeCharts()
{
var timeAxis = new Axis
{
Labeler = value => new DateTime((long)value).ToString("MM-dd HH:mm"),
UnitWidth = TimeSpan.FromHours(1).Ticks,
LabelsRotation = 45
};
// 流量图表
InflowSeries = new ISeries[] { CreateLineSeries(T("InflowSeries"), SKColors.CornflowerBlue) };
OutflowSeries = new ISeries[] { CreateLineSeries(T("OutflowSeries"), SKColors.MediumSeaGreen) };
XAxes = new[] { timeAxis };
YAxes = new[] { new Axis { Name = "m³/h", MinLimit = 0 } };
// 液位图表
TankLevelSeries = new ISeries[]
{
CreateLineSeries(T("Tank1Name"), SKColors.CornflowerBlue),
CreateLineSeries(T("Tank2Name"), SKColors.MediumSeaGreen),
CreateLineSeries(T("Tank3Name"), SKColors.Orange),
CreateLineSeries(T("Tank4Name"), SKColors.MediumPurple),
};
TankYAxes = new[] { new Axis { Name = "%", MinLimit = 0, MaxLimit = 100 } };
// 泵频率图表
PumpFreqSeries = new ISeries[]
{
CreateLineSeries(T("Pump1Name"), SKColors.CornflowerBlue),
CreateLineSeries(T("Pump2Name"), SKColors.MediumSeaGreen),
CreateLineSeries(T("Pump3Name"), SKColors.Orange),
CreateLineSeries(T("Pump4Name"), SKColors.MediumPurple),
CreateLineSeries(T("Pump5Name"), SKColors.Crimson),
};
PumpYAxes = new[] { new Axis { Name = "Hz", MinLimit = 0 } };
}
private static LineSeries CreateLineSeries(string name, SKColor color)
{
return new LineSeries
{
Name = name,
Values = new List(),
Stroke = new SolidColorPaint(color, 2),
Fill = null,
GeometryStroke = null,
GeometrySize = 0
};
}
///
/// 预设时间范围
///
[RelayCommand]
private void SetTimeRange(string range)
{
EndDate = DateTimeOffset.Now;
StartDate = range switch
{
"1h" => DateTimeOffset.Now.AddHours(-1),
"4h" => DateTimeOffset.Now.AddHours(-4),
"8h" => DateTimeOffset.Now.AddHours(-8),
"24h" => DateTimeOffset.Now.AddHours(-24),
"7d" => DateTimeOffset.Now.AddDays(-7),
"30d" => DateTimeOffset.Now.AddDays(-30),
_ => DateTimeOffset.Now.AddDays(-1)
};
_ = LoadFlowRecordsAsync();
}
///
/// 加载历史数据
///
[RelayCommand]
private async Task LoadFlowRecordsAsync()
{
try
{
var start = StartDate?.DateTime ?? DateTime.Now.AddDays(-1);
var end = EndDate?.DateTime ?? DateTime.Now;
// 加载流量记录
var records = await DatabaseService.Db.Queryable()
.Where(r => r.RecordTime >= start && r.RecordTime <= end)
.OrderByDescending(r => r.RecordTime)
.ToListAsync();
FlowRecords = records;
// 加载模拟量记录
var analogRecords = await DatabaseService.Db.Queryable()
.Where(r => r.RecordTime >= start && r.RecordTime <= end)
.OrderBy(r => r.RecordTime)
.ToListAsync();
UpdateFlowChart(records);
UpdateTankLevelChart(analogRecords);
UpdatePumpFreqChart(analogRecords);
Log.Debug("加载了 {Flow} 条流量记录, {Analog} 条模拟量记录", records.Count, analogRecords.Count);
}
catch (Exception ex)
{
Log.Error(ex, "加载历史数据失败");
}
}
private void UpdateFlowChart(List records)
{
var inflowData = records
.GroupBy(r => GetTimeGroupKey(r.RecordTime))
.Select(g => new DateTimePoint(g.Key, g.Average(r => r.InflowRate)))
.OrderBy(p => p.DateTime)
.ToList();
var outflowData = records
.GroupBy(r => GetTimeGroupKey(r.RecordTime))
.Select(g => new DateTimePoint(g.Key, g.Average(r => r.OutflowRate)))
.OrderBy(p => p.DateTime)
.ToList();
InflowSeries[0].Values = inflowData;
OutflowSeries[0].Values = outflowData;
}
private void UpdateTankLevelChart(List records)
{
var tags = new[] { "Tank1Level", "Tank2Level", "Tank3Level", "Tank4Level" };
for (int i = 0; i < tags.Length && i < TankLevelSeries.Length; i++)
{
var data = records
.Where(r => r.TagName == tags[i])
.GroupBy(r => GetTimeGroupKey(r.RecordTime))
.Select(g => new DateTimePoint(g.Key, g.Average(r => r.Value)))
.OrderBy(p => p.DateTime)
.ToList();
TankLevelSeries[i].Values = data;
}
}
private void UpdatePumpFreqChart(List records)
{
var tags = new[] { "Pump1Freq", "Pump2Freq", "Pump3Freq", "Pump4Freq", "Pump5Freq" };
for (int i = 0; i < tags.Length && i < PumpFreqSeries.Length; i++)
{
var data = records
.Where(r => r.TagName == tags[i])
.GroupBy(r => GetTimeGroupKey(r.RecordTime))
.Select(g => new DateTimePoint(g.Key, g.Average(r => r.Value)))
.OrderBy(p => p.DateTime)
.ToList();
PumpFreqSeries[i].Values = data;
}
}
///
/// 根据时间范围自动选择分组精度
///
private static DateTime GetTimeGroupKey(DateTime time)
{
return time.Date.AddHours(time.Hour).AddMinutes(time.Minute / 5 * 5);
}
[RelayCommand]
private async Task ExportDataAsync()
{
try
{
var filePath = $"FlowRecords_{DateTime.Now:yyyyMMdd_HHmmss}.csv";
var lines = new List
{
"时间,进水流量(m³/h),出水流量(m³/h),累计进水量(m³),累计出水量(m³),备注"
};
foreach (var record in FlowRecords)
{
lines.Add($"{record.RecordTime:yyyy-MM-dd HH:mm:ss},{record.InflowRate},{record.OutflowRate},{record.TotalInflow},{record.TotalOutflow},{record.Remark}");
}
await File.WriteAllLinesAsync(filePath, lines);
Log.Information("流量记录已导出到: {FilePath}", filePath);
}
catch (Exception ex)
{
Log.Error(ex, "导出流量记录失败");
}
}
[RelayCommand]
private async Task ClearOldDataAsync()
{
try
{
var cutoffDate = DateTime.Now.AddDays(-30);
await DatabaseService.Db.Deleteable()
.Where(r => r.RecordTime < cutoffDate)
.ExecuteCommandAsync();
await DatabaseService.Db.Deleteable()
.Where(r => r.RecordTime < cutoffDate)
.ExecuteCommandAsync();
await LoadFlowRecordsAsync();
Log.Information("已清除 30 天前的历史数据");
}
catch (Exception ex)
{
Log.Error(ex, "清除旧数据失败");
}
}
public new void Dispose()
{
if (_disposed) return;
_disposed = true;
PlcPollingService.Instance.DataUpdated -= OnDataUpdated;
base.Dispose();
}
}