Files
ADP/MonitorModule/ViewModels/RecordViewModel.cs

411 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Common.Attributes;
using DeviceCommand.Base;
using Logger;
using Model.Entity;
using ORM;
using SqlSugar;
using System.Collections.ObjectModel;
using System.Data;
using System.IO;
using System.Reflection;
using System.Text;
using System.Windows.Input;
using UIShare.GlobalVariable;
using UIShare.PubEvent;
using UIShare.ViewModelBase;
namespace MonitorModule.ViewModels
{
/// <summary>
/// 记录界面:查询 MonitorValueEntity 表中的历史监测数据。
/// 功能:监测项筛选 / 台架筛选 / 日期筛选 / 分页 / 导出 CSV。
/// 使用 SqlSugarContext.DbContext 全局单例,与写入端共用同一数据库连接。
/// </summary>
public class RecordViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
{
#region
public bool KeepAlive => true;
public ScopedContext _scopedContext { get; set; }
public GlobalInfo _globalInfo { get; }
public string TestStatus
{
get => _testStatus;
set => SetProperty(ref _testStatus, value);
}
/// <summary>监测项名称下拉列表</summary>
public ObservableCollection<string> MonitorNames
{
get => _monitorNames;
set => SetProperty(ref _monitorNames, value);
}
/// <summary>选中的监测项null = 全部)</summary>
public string? SelectedMonitorName
{
get => _selectedMonitorName;
set => SetProperty(ref _selectedMonitorName, value);
}
/// <summary>台架(作用域)下拉列表</summary>
public ObservableCollection<string> ScopeNames
{
get => _scopeNames;
set => SetProperty(ref _scopeNames, value);
}
/// <summary>选中的台架null = 全部)</summary>
public string? SelectedScope
{
get => _selectedScope;
set => SetProperty(ref _selectedScope, value);
}
/// <summary>查询日期null = 全部日期)</summary>
public DateTime? SelectedStartDate
{
get => _selectedStartDate;
set => SetProperty(ref _selectedStartDate, value);
}
public DateTime? SelectedEndDate
{
get => _selectedEndDate;
set => SetProperty(ref _selectedEndDate, value);
}
public DataTable ResultTable
{
get => _resultTable;
set => SetProperty(ref _resultTable, value);
}
public string StatusMessage
{
get => _statusMessage;
set => SetProperty(ref _statusMessage, value);
}
public int PageIndex
{
get => _pageIndex;
set => SetProperty(ref _pageIndex, value);
}
public int PageSize
{
get => _pageSize;
set
{
if (SetProperty(ref _pageSize, value <= 0 ? 50 : value))
{
RaisePropertyChanged(nameof(TotalPages));
PageIndex = 1;
Query();
}
}
}
public long TotalCount
{
get => _totalCount;
set
{
SetProperty(ref _totalCount, value);
RaisePropertyChanged(nameof(TotalPages));
}
}
public int TotalPages
{
get
{
if (PageSize <= 0) return 1;
var pages = (int)((TotalCount + PageSize - 1) / PageSize);
return pages <= 0 ? 1 : pages;
}
}
#endregion
#region
public ICommand LoadedCommand { get; }
public ICommand QueryCommand { get; }
public ICommand ClearFilterCommand { get; }
public ICommand FirstPageCommand { get; }
public ICommand PrevPageCommand { get; }
public ICommand NextPageCommand { get; }
public ICommand LastPageCommand { get; }
public ICommand ExportCsvCommand { get; }
public ICommand RefreshCommand { get; }
#endregion
#region
private IScopedProvider _scope;
private string _testStatus = string.Empty;
private ObservableCollection<string> _monitorNames = new();
private string? _selectedMonitorName;
private ObservableCollection<string> _scopeNames = new(new[] { "全部", "TestCell1", "TestCell2", "TestCell3", "TestCell4", "TestCell5", "TestCell6", "TestCell7", "TestCell8" });
private string? _selectedScope = "全部";
private DateTime? _selectedStartDate;
private DateTime? _selectedEndDate;
private DataTable _resultTable = new();
private string _statusMessage = "未连接";
private int _pageIndex = 1;
private int _pageSize = 50;
private long _totalCount;
private bool IsInitiated = false;
#endregion
public RecordViewModel(IContainerExtension container) : base(container)
{
_globalInfo = container.Resolve<GlobalInfo>();
LoadedCommand = new DelegateCommand(LoadFilterOptions);
QueryCommand = new DelegateCommand(() => { PageIndex = 1; Query(); });
ClearFilterCommand = new DelegateCommand(ClearFilter);
FirstPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex = 1; Query(); } });
PrevPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex--; Query(); } });
NextPageCommand = new DelegateCommand(() => { if (PageIndex < TotalPages) { PageIndex++; Query(); } });
LastPageCommand = new DelegateCommand(() => { if (PageIndex < TotalPages) { PageIndex = TotalPages; Query(); } });
ExportCsvCommand = new DelegateCommand(ExportCsv);
RefreshCommand = new DelegateCommand(OnExpand);
}
public void Dispose()
{
_scope?.Dispose();
}
#region
/// <summary>
/// 加载筛选条件:监测项通过反射发现(与 MonitorViewModel 一致),台架从数据库查询。
/// </summary>
private void LoadFilterOptions()
{
// 监测项:反射发现当前作用域所有带 [Monitorable] 的设备方法
DiscoverMonitorNames();
Query();
}
/// <summary>
/// 反射扫描当前作用域 DeviceManager 中所有带 [Monitorable] 特性的设备方法,
/// 生成与 MonitorViewModel 完全一致的 DisplayName 列表,填充到下拉框。
/// 这样即使数据库还没数据,用户也能看到所有可筛选的监测项。
/// </summary>
private void DiscoverMonitorNames()
{
MonitorNames = new ObservableCollection<string>();
if (_scope == null) return;
try
{
var deviceManager = _scope.Resolve<DeviceManager>();
if (deviceManager?.DeviceMap == null || deviceManager.DeviceMap.Count == 0)
{
StatusMessage = "当前工位无可用设备";
return;
}
// 构建 设备实例 → 硬件指纹 的反向查找表
var instanceToFp = new Dictionary<object, string>(ReferenceEqualityComparer.Instance);
foreach (var poolEntry in _globalInfo.HardwarePool)
{
var lazy = poolEntry.Value;
if (lazy.IsValueCreated && lazy.Value != null)
instanceToFp[lazy.Value] = poolEntry.Key;
}
var names = new List<string>();
foreach (var kvp in deviceManager.DeviceMap)
{
string deviceName = kvp.Key;
var device = kvp.Value;
var deviceType = device.GetType();
// 只列出在硬件指纹池中注册的设备
if (!instanceToFp.ContainsKey(device))
continue;
var methods = deviceType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(m =>
{
if (m.GetCustomAttribute<MonitorableAttribute>() == null) return false;
if (m.ReturnType != typeof(Task<string>)) return false;
var parms = m.GetParameters();
return parms.Length == 0 ||
(parms.Length == 1 && parms[0].ParameterType == typeof(CancellationToken));
});
foreach (var method in methods)
{
var attr = method.GetCustomAttribute<MonitorableAttribute>();
string displayName = !string.IsNullOrEmpty(attr?.Description)
? $"{deviceName}.{attr.Description}"
: $"{deviceName}.{method.Name}";
names.Add(displayName);
}
}
names.Sort();
MonitorNames = new ObservableCollection<string>(names);
}
catch (Exception ex)
{
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope, $"反射发现监测项失败:{ex.Message}");
}
}
/// <summary>清除所有筛选条件</summary>
private void ClearFilter()
{
SelectedMonitorName = null;
SelectedScope = "全部";
SelectedStartDate = null;
SelectedEndDate = null;
PageIndex = 1;
Query();
}
#endregion
#region
/// <summary>
/// 按当前筛选条件分页查询 MonitorValueEntity。
/// </summary>
private void Query()
{
try
{
var db = SqlSugarContext.DbContext;
// 预计算日期范围,避免在表达式树中访问可空属性
DateTime? dateStart = SelectedStartDate?.Date;
DateTime? dateEnd = SelectedEndDate?.Date.AddDays(1);
var query = db.Queryable<MonitorValueEntity>()
.WhereIF(!string.IsNullOrEmpty(SelectedMonitorName), x => x.MonitorName == SelectedMonitorName)
.WhereIF(!string.IsNullOrEmpty(SelectedScope) && SelectedScope != "全部", x => x.Scope == SelectedScope)
.WhereIF(dateStart.HasValue, x => x.CreateTime >= dateStart && x.CreateTime < dateEnd!.Value)
.OrderBy(x => x.CreateTime, OrderByType.Desc);
TotalCount = query.Count();
// 修正越界
if (PageIndex > TotalPages) PageIndex = TotalPages;
if (PageIndex < 1) PageIndex = 1;
int skip = (PageIndex - 1) * PageSize;
ResultTable = query
.Skip(skip)
.Take(PageSize)
.ToDataTable();
StatusMessage = $"共 {TotalCount} 行 第 {PageIndex}/{TotalPages} 页";
}
catch (Exception ex)
{
LoggerHelper.ErrorWithNotify(TestStatus, $"查询失败:{ex.Message}");
StatusMessage = $"查询失败:{ex.Message}";
ResultTable = new DataTable();
TotalCount = 0;
}
}
#endregion
#region CSV
/// <summary>
/// 按当前筛选条件导出全部结果到 CSV不分页
/// </summary>
private void ExportCsv()
{
try
{
var db = SqlSugarContext.DbContext;
DateTime? dateStart = SelectedStartDate?.Date;
DateTime? dateEnd = SelectedEndDate?.Date.AddDays(1);
var query = db.Queryable<MonitorValueEntity>()
.WhereIF(!string.IsNullOrEmpty(SelectedMonitorName), x => x.MonitorName == SelectedMonitorName)
.WhereIF(!string.IsNullOrEmpty(SelectedScope) && SelectedScope != "全部", x => x.Scope == SelectedScope)
.WhereIF(dateStart.HasValue, x => x.CreateTime >= dateStart && x.CreateTime < dateEnd!.Value)
.OrderBy(x => x.CreateTime, OrderByType.Desc);
var dt = query.ToDataTable();
if (dt.Rows.Count == 0)
{
StatusMessage = "无可导出数据";
return;
}
var dlg = new Microsoft.Win32.SaveFileDialog
{
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
FileName = $"MonitorData_{DateTime.Now:yyyyMMdd_HHmmss}.csv",
Title = "导出监测数据为 CSV"
};
if (dlg.ShowDialog() != true) return;
WriteCsv(dlg.FileName, dt);
StatusMessage = $"导出完成:{dlg.FileName}{dt.Rows.Count} 行)";
LoggerHelper.InfoWithNotify(TestStatus, $"工位 [{TestStatus}] 导出监测数据至 {dlg.FileName},共 {dt.Rows.Count} 行");
}
catch (Exception ex)
{
LoggerHelper.ErrorWithNotify(TestStatus, $"导出失败:{ex.Message}");
StatusMessage = $"导出失败:{ex.Message}";
}
}
private static void WriteCsv(string path, DataTable dt)
{
// 写 UTF-8 BOM 让 Excel 直接识别中文
using var sw = new StreamWriter(path, false, new UTF8Encoding(true));
// 表头
sw.WriteLine(string.Join(",", dt.Columns.Cast<DataColumn>().Select(c => Escape(c.ColumnName))));
// 行
foreach (DataRow row in dt.Rows)
{
sw.WriteLine(string.Join(",", row.ItemArray.Select(v => Escape(v?.ToString() ?? string.Empty))));
}
}
private static string Escape(string field)
{
if (field.Contains('"') || field.Contains(',') || field.Contains('\r') || field.Contains('\n'))
{
return "\"" + field.Replace("\"", "\"\"") + "\"";
}
return field;
}
#endregion
#region
private void OnExpand()
{
if (string.IsNullOrEmpty(TestStatus)) return;
_globalInfo.CurrentScope = TestStatus;
_eventAggregator.GetEvent<ExpandViewEvent>().Publish(TestStatus);
}
#endregion
#region
public override void OnNavigatedTo(NavigationContext navigationContext)
{
base.OnNavigatedTo(navigationContext);
if (!IsInitiated && navigationContext.Parameters.ContainsKey("Name"))
{
TestStatus = navigationContext.Parameters.GetValue<string>("Name");
_scope = _globalInfo.ScopeDic[TestStatus];
_scopedContext = _scope.Resolve<ScopedContext>();
IsInitiated = true;
}
LoadFilterOptions();
}
#endregion
}
}