更新20260601,数据持久处理
This commit is contained in:
@@ -31,6 +31,7 @@ namespace ConeCalorimeter
|
||||
experimentDataService,
|
||||
_tcpDeviceConnectionService,
|
||||
new NpoiReportExportService(),
|
||||
new JsonReportInputPersistenceService(),
|
||||
new NpoiRealtimeDataExportService(),
|
||||
new HelpDialogService());
|
||||
}
|
||||
|
||||
8
ConeCalorimeter/Models/ReportManualEntryFieldState.cs
Normal file
8
ConeCalorimeter/Models/ReportManualEntryFieldState.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace ConeCalorimeter.Models;
|
||||
|
||||
public sealed class ReportManualEntryFieldState
|
||||
{
|
||||
public string Value { get; set; } = string.Empty;
|
||||
|
||||
public bool IsAutoFilled { get; set; }
|
||||
}
|
||||
8
ConeCalorimeter/Models/ReportManualEntryState.cs
Normal file
8
ConeCalorimeter/Models/ReportManualEntryState.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace ConeCalorimeter.Models;
|
||||
|
||||
public sealed class ReportManualEntryState
|
||||
{
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
public Dictionary<string, ReportManualEntryFieldState> Fields { get; set; } = [];
|
||||
}
|
||||
12
ConeCalorimeter/Services/IReportInputPersistenceService.cs
Normal file
12
ConeCalorimeter/Services/IReportInputPersistenceService.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using ConeCalorimeter.Models;
|
||||
|
||||
namespace ConeCalorimeter.Services;
|
||||
|
||||
public interface IReportInputPersistenceService
|
||||
{
|
||||
string StoragePath { get; }
|
||||
|
||||
IReadOnlyDictionary<string, ReportManualEntryFieldState> Load();
|
||||
|
||||
void Save(IReadOnlyDictionary<string, ReportManualEntryFieldState> fields);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using ConeCalorimeter.Models;
|
||||
using Serilog;
|
||||
|
||||
namespace ConeCalorimeter.Services;
|
||||
|
||||
public sealed class JsonReportInputPersistenceService : IReportInputPersistenceService
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
public JsonReportInputPersistenceService()
|
||||
: this(Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"ConeCalorimeter",
|
||||
"report-manual-input.json"))
|
||||
{
|
||||
}
|
||||
|
||||
public JsonReportInputPersistenceService(string storagePath)
|
||||
{
|
||||
StoragePath = storagePath;
|
||||
}
|
||||
|
||||
public string StoragePath { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, ReportManualEntryFieldState> Load()
|
||||
{
|
||||
if (!File.Exists(StoragePath))
|
||||
{
|
||||
return new Dictionary<string, ReportManualEntryFieldState>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = File.OpenRead(StoragePath);
|
||||
var state = JsonSerializer.Deserialize<ReportManualEntryState>(stream, JsonOptions);
|
||||
return state?.Fields ?? new Dictionary<string, ReportManualEntryFieldState>();
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or JsonException)
|
||||
{
|
||||
Log.Warning(ex, "Report manual input JSON load failed. Path={Path}.", StoragePath);
|
||||
return new Dictionary<string, ReportManualEntryFieldState>();
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(IReadOnlyDictionary<string, ReportManualEntryFieldState> fields)
|
||||
{
|
||||
var directory = Path.GetDirectoryName(StoragePath);
|
||||
if (!string.IsNullOrWhiteSpace(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
var state = new ReportManualEntryState
|
||||
{
|
||||
Fields = fields.ToDictionary(
|
||||
pair => pair.Key,
|
||||
pair => new ReportManualEntryFieldState
|
||||
{
|
||||
Value = pair.Value.Value ?? string.Empty,
|
||||
IsAutoFilled = pair.Value.IsAutoFilled
|
||||
})
|
||||
};
|
||||
|
||||
var tempPath = $"{StoragePath}.tmp";
|
||||
using (var stream = File.Create(tempPath))
|
||||
{
|
||||
JsonSerializer.Serialize(stream, state, JsonOptions);
|
||||
}
|
||||
|
||||
File.Move(tempPath, StoragePath, true);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ public sealed class MainViewModel : ObservableObject
|
||||
IExperimentDataService experimentDataService,
|
||||
ITcpDeviceConnectionService tcpDeviceConnectionService,
|
||||
IReportExportService reportExportService,
|
||||
IReportInputPersistenceService reportInputPersistenceService,
|
||||
IRealtimeDataExportService realtimeDataExportService,
|
||||
IHelpDialogService helpDialogService)
|
||||
{
|
||||
@@ -28,6 +29,7 @@ public sealed class MainViewModel : ObservableObject
|
||||
var reportPage = new ReportPageViewModel(
|
||||
experimentDataService,
|
||||
reportExportService,
|
||||
reportInputPersistenceService,
|
||||
tcpDeviceConnectionService);
|
||||
NavigationItems = [];
|
||||
|
||||
|
||||
@@ -19,6 +19,12 @@ public sealed class ReportFieldViewModel : ObservableObject
|
||||
|
||||
public string Label { get; }
|
||||
|
||||
public bool IsAutoFilled
|
||||
{
|
||||
get => _isAutoFilled;
|
||||
private set => SetProperty(ref _isAutoFilled, value);
|
||||
}
|
||||
|
||||
public string Value
|
||||
{
|
||||
get => _value;
|
||||
@@ -26,7 +32,7 @@ public sealed class ReportFieldViewModel : ObservableObject
|
||||
{
|
||||
if (SetProperty(ref _value, value) && !_isSettingAutoValue)
|
||||
{
|
||||
_isAutoFilled = false;
|
||||
IsAutoFilled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,6 +52,14 @@ public sealed class ReportFieldViewModel : ObservableObject
|
||||
_isSettingAutoValue = true;
|
||||
Value = value;
|
||||
_isSettingAutoValue = false;
|
||||
_isAutoFilled = true;
|
||||
IsAutoFilled = true;
|
||||
}
|
||||
|
||||
public void SetPersistedValue(string value, bool isAutoFilled)
|
||||
{
|
||||
_isSettingAutoValue = true;
|
||||
Value = value;
|
||||
_isSettingAutoValue = false;
|
||||
IsAutoFilled = isAutoFilled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
@@ -22,20 +23,25 @@ public sealed class ReportPageViewModel : PageViewModel
|
||||
|
||||
private readonly IExperimentDataService _experimentDataService;
|
||||
private readonly IReportExportService _reportExportService;
|
||||
private readonly IReportInputPersistenceService _reportInputPersistenceService;
|
||||
private readonly ITcpDeviceConnectionService _tcpDeviceConnectionService;
|
||||
private ModbusFloatByteOrder? _cFactorByteOrder;
|
||||
private string _statusText = "报表信息可手动录入,导出时会合并当前采集数据。";
|
||||
private string _statusText = "报表信息可手动录入,内容会自动保存,导出时合并当前采集数据。";
|
||||
|
||||
public ReportPageViewModel(
|
||||
IExperimentDataService experimentDataService,
|
||||
IReportExportService reportExportService,
|
||||
IReportInputPersistenceService reportInputPersistenceService,
|
||||
ITcpDeviceConnectionService tcpDeviceConnectionService) : base("报表")
|
||||
{
|
||||
_experimentDataService = experimentDataService;
|
||||
_reportExportService = reportExportService;
|
||||
_reportInputPersistenceService = reportInputPersistenceService;
|
||||
_tcpDeviceConnectionService = tcpDeviceConnectionService;
|
||||
|
||||
Sections = CreateSections();
|
||||
LoadPersistedReportInput();
|
||||
AttachReportFieldPersistence();
|
||||
SummaryItems =
|
||||
[
|
||||
new ReportSummaryItemViewModel("记录数"),
|
||||
@@ -170,7 +176,7 @@ public sealed class ReportPageViewModel : PageViewModel
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshCurrentCValueReportField(overwriteManual: true);
|
||||
RefreshCurrentCValueReportField(overwriteManual: false);
|
||||
var input = BuildInput();
|
||||
var defaultName = BuildDefaultFileName(input);
|
||||
var dialog = new SaveFileDialog
|
||||
@@ -237,6 +243,67 @@ public sealed class ReportPageViewModel : PageViewModel
|
||||
return input;
|
||||
}
|
||||
|
||||
private void LoadPersistedReportInput()
|
||||
{
|
||||
var persistedFields = _reportInputPersistenceService.Load();
|
||||
if (persistedFields.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var field in Sections.SelectMany(section => section.Fields))
|
||||
{
|
||||
if (!persistedFields.TryGetValue(field.Key, out var state))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
field.SetPersistedValue(state.Value ?? string.Empty, state.IsAutoFilled);
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachReportFieldPersistence()
|
||||
{
|
||||
foreach (var field in Sections.SelectMany(section => section.Fields))
|
||||
{
|
||||
field.PropertyChanged += ReportFieldPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReportFieldPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != nameof(ReportFieldViewModel.Value)
|
||||
&& e.PropertyName != nameof(ReportFieldViewModel.IsAutoFilled))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SaveReportInput();
|
||||
}
|
||||
|
||||
private void SaveReportInput()
|
||||
{
|
||||
try
|
||||
{
|
||||
var state = Sections
|
||||
.SelectMany(section => section.Fields)
|
||||
.ToDictionary(
|
||||
field => field.Key,
|
||||
field => new ReportManualEntryFieldState
|
||||
{
|
||||
Value = field.Value,
|
||||
IsAutoFilled = field.IsAutoFilled
|
||||
});
|
||||
|
||||
_reportInputPersistenceService.Save(state);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText = $"报表录入保存失败:{ex.Message}";
|
||||
Log.Error(ex, "Report manual input JSON save failed. Path={Path}.", _reportInputPersistenceService.StoragePath);
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildDefaultFileName(ReportInput input)
|
||||
{
|
||||
var name = FirstNonEmpty(input.ReportName, input.FileName, input.SampleName, "锥形量热仪测试报告");
|
||||
|
||||
Reference in New Issue
Block a user