添加项目文件。

This commit is contained in:
“hsc”
2026-07-29 13:12:27 +08:00
parent d6da766e2d
commit 8cf45d36b9
297 changed files with 35814 additions and 0 deletions

View File

@@ -0,0 +1,410 @@
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
}
}