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 { /// /// 记录界面:查询 MonitorValueEntity 表中的历史监测数据。 /// 功能:监测项筛选 / 台架筛选 / 日期筛选 / 分页 / 导出 CSV。 /// 使用 SqlSugarContext.DbContext 全局单例,与写入端共用同一数据库连接。 /// 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); } /// 监测项名称下拉列表 public ObservableCollection MonitorNames { get => _monitorNames; set => SetProperty(ref _monitorNames, value); } /// 选中的监测项(null = 全部) public string? SelectedMonitorName { get => _selectedMonitorName; set => SetProperty(ref _selectedMonitorName, value); } /// 台架(作用域)下拉列表 public ObservableCollection ScopeNames { get => _scopeNames; set => SetProperty(ref _scopeNames, value); } /// 选中的台架(null = 全部) public string? SelectedScope { get => _selectedScope; set => SetProperty(ref _selectedScope, value); } /// 查询日期(null = 全部日期) public DateTime? SelectedDate { get => _selectedDate; set => SetProperty(ref _selectedDate, 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 _monitorNames = new(); private string? _selectedMonitorName; private ObservableCollection _scopeNames = new(new[] { "全部", "TestCell1", "TestCell2", "TestCell3", "TestCell4", "TestCell5", "TestCell6", "TestCell7", "TestCell8" }); private string? _selectedScope = "全部"; private DateTime? _selectedDate; 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(); 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 筛选条件加载 /// /// 加载筛选条件:监测项通过反射发现(与 MonitorViewModel 一致),台架从数据库查询。 /// private void LoadFilterOptions() { // 监测项:反射发现当前作用域所有带 [Monitorable] 的设备方法 DiscoverMonitorNames(); Query(); } /// /// 反射扫描当前作用域 DeviceManager 中所有带 [Monitorable] 特性的设备方法, /// 生成与 MonitorViewModel 完全一致的 DisplayName 列表,填充到下拉框。 /// 这样即使数据库还没数据,用户也能看到所有可筛选的监测项。 /// private void DiscoverMonitorNames() { MonitorNames = new ObservableCollection(); if (_scope == null) return; try { var deviceManager = _scope.Resolve(); if (deviceManager?.DeviceMap == null || deviceManager.DeviceMap.Count == 0) { StatusMessage = "当前工位无可用设备"; return; } // 构建 设备实例 → 硬件指纹 的反向查找表 var instanceToFp = new Dictionary(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(); 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() == null) return false; if (m.ReturnType != typeof(Task)) 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(); string displayName = !string.IsNullOrEmpty(attr?.Description) ? $"{deviceName}.{attr.Description}" : $"{deviceName}.{method.Name}"; names.Add(displayName); } } names.Sort(); MonitorNames = new ObservableCollection(names); } catch (Exception ex) { LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope, $"反射发现监测项失败:{ex.Message}"); } } /// 清除所有筛选条件 private void ClearFilter() { SelectedMonitorName = null; SelectedScope = "全部"; SelectedDate = null; PageIndex = 1; Query(); } #endregion #region 查询 /// /// 按当前筛选条件分页查询 MonitorValueEntity。 /// private void Query() { try { var db = SqlSugarContext.DbContext; // 预计算日期范围,避免在表达式树中访问可空属性 DateTime? dateStart = SelectedDate?.Date; DateTime? dateEnd = dateStart?.AddDays(1); var query = db.Queryable() .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 /// /// 按当前筛选条件导出全部结果到 CSV(不分页)。 /// private void ExportCsv() { try { var db = SqlSugarContext.DbContext; DateTime? dateStart = SelectedDate?.Date; DateTime? dateEnd = dateStart?.AddDays(1); var query = db.Queryable() .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().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().Publish(TestStatus); } #endregion #region 重写 public override void OnNavigatedTo(NavigationContext navigationContext) { base.OnNavigatedTo(navigationContext); if (!IsInitiated && navigationContext.Parameters.ContainsKey("Name")) { TestStatus = navigationContext.Parameters.GetValue("Name"); _scope = _globalInfo.ScopeDic[TestStatus]; _scopedContext = _scope.Resolve(); IsInitiated = true; } LoadFilterOptions(); } #endregion } }