| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- 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;
- /// <summary>
- /// 流量记录视图 ViewModel
- /// </summary>
- 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<FlowRecord> _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);
- }
- /// <summary>
- /// 添加实时数据点
- /// </summary>
- 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<DateTimePoint> lineSeries)
- {
- var values = lineSeries.Values?.ToList() ?? new List<DateTimePoint>();
- values.Add(new DateTimePoint(time, value));
- if (values.Count > 120) values.RemoveAt(0);
- lineSeries.Values = values.ToArray();
- }
- }
- /// <summary>
- /// 初始化所有图表
- /// </summary>
- 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<DateTimePoint> CreateLineSeries(string name, SKColor color)
- {
- return new LineSeries<DateTimePoint>
- {
- Name = name,
- Values = new List<DateTimePoint>(),
- Stroke = new SolidColorPaint(color, 2),
- Fill = null,
- GeometryStroke = null,
- GeometrySize = 0
- };
- }
- /// <summary>
- /// 预设时间范围
- /// </summary>
- [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();
- }
- /// <summary>
- /// 加载历史数据
- /// </summary>
- [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<FlowRecord>()
- .Where(r => r.RecordTime >= start && r.RecordTime <= end)
- .OrderByDescending(r => r.RecordTime)
- .ToListAsync();
- FlowRecords = records;
- // 加载模拟量记录
- var analogRecords = await DatabaseService.Db.Queryable<AnalogRecord>()
- .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<FlowRecord> 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<AnalogRecord> 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<AnalogRecord> 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;
- }
- }
- /// <summary>
- /// 根据时间范围自动选择分组精度
- /// </summary>
- 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<string>
- {
- "时间,进水流量(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<FlowRecord>()
- .Where(r => r.RecordTime < cutoffDate)
- .ExecuteCommandAsync();
- await DatabaseService.Db.Deleteable<AnalogRecord>()
- .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();
- }
- }
|