using Prism.Commands; using Prism.Mvvm; using System; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using TeamAAS_VP.Interfaces; using TeamAAS_VP.Models; using TeamAAS_VP.Models.Product; namespace TeamAAS_VP.ViewModels.Statistics { public class LockResultRecoredQueryViewModel : BindableBase { private readonly IProductService _productService; private readonly ISystemDatabaseService _systemDatabaseService; public LockResultRecoredQueryViewModel(IProductService productService, ISystemDatabaseService systemDatabaseService) { _productService = productService; _systemDatabaseService = systemDatabaseService; PageSize = 20; PageIndex = 1; SearchCommand = new DelegateCommand(async () => await ExecuteSearchCommand(), CanSearch).ObservesProperty(() => SelectedProduct).ObservesProperty(() => StartDate).ObservesProperty(() => EndDate).ObservesProperty(() => IsSearching); PrevPageCommand = new DelegateCommand(async () => { if (PageIndex>1) { PageIndex--; await ExecuteSearchCommand(); } }).ObservesProperty(() => PageIndex); NextPageCommand = new DelegateCommand(async () => { if (PageIndex * PageSize < TotalCount) { PageIndex++; await ExecuteSearchCommand(); } }).ObservesProperty(() => PageIndex).ObservesProperty(() => TotalCount); ExportCommand = new DelegateCommand(async () => await ExecuteExportCommand(), CanExport).ObservesProperty(() => Results).ObservesProperty(() => IsSearching); AllProduct = new ObservableCollection(_productService.GetAllProducts()); StartDate = DateTime.Now.Date; EndDate = DateTime.Now.Date.AddDays(1); } #region Properties private ObservableCollection _AllProduct; public ObservableCollection AllProduct { get { return _AllProduct; } set { SetProperty(ref _AllProduct, value); } } private ProductModel _SelectedProduct; public ProductModel SelectedProduct { get { return _SelectedProduct; } set { SetProperty(ref _SelectedProduct, value); } } private DateTime? _StartDate; public DateTime? StartDate { get { return _StartDate; } set { SetProperty(ref _StartDate, value); } } private DateTime? _EndDate; public DateTime? EndDate { get { return _EndDate; } set { SetProperty(ref _EndDate, value); } } private int? _ScrewNumber; public int? ScrewNumber { get { return _ScrewNumber; } set { SetProperty(ref _ScrewNumber, value); } } private string _ProductNumber; public string ProductNumber { get { return _ProductNumber; } set { SetProperty(ref _ProductNumber, value); } } private ObservableCollection _Results = new ObservableCollection(); public ObservableCollection Results { get { return _Results; } set { SetProperty(ref _Results, value); } } private int _PageIndex; public int PageIndex { get { return _PageIndex; } set { SetProperty(ref _PageIndex, value); } } private int _PageSize; public int PageSize { get { return _PageSize; } set { SetProperty(ref _PageSize, value); } } private int _TotalCount; public int TotalCount { get { return _TotalCount; } set { SetProperty(ref _TotalCount, value); } } private bool _IsSearching = false; public bool IsSearching { get { return _IsSearching; } set { SetProperty(ref _IsSearching, value); } } #endregion #region Commands public DelegateCommand SearchCommand { get; private set; } public DelegateCommand PrevPageCommand { get; private set; } public DelegateCommand NextPageCommand { get; private set; } public DelegateCommand ExportCommand { get; private set; } #endregion private bool CanSearch() { if (IsSearching) return false; if (SelectedProduct == null && string.IsNullOrWhiteSpace(ProductNumber) && !ScrewNumber.HasValue) return false; if (!StartDate.HasValue || !EndDate.HasValue) return false; return true; } private bool CanExport() { // allow export even if current page is empty; user may want to export all matching records return !IsSearching && StartDate.HasValue && EndDate.HasValue && (SelectedProduct != null || !string.IsNullOrWhiteSpace(ProductNumber) || ScrewNumber.HasValue); } private async Task ExecuteSearchCommand() { try { IsSearching = true; // determine time range (use full days) var start = StartDate.HasValue ? StartDate.Value.Date : DateTime.MinValue; var end = EndDate.HasValue ? EndDate.Value.Date.AddDays(1) : DateTime.MaxValue; string productNumberFilter = null; if (!string.IsNullOrWhiteSpace(ProductNumber)) productNumberFilter = ProductNumber.Trim(); // if SelectedProduct is selected, use its Name as productNumber? The user requested select product name then time range; productNumber precise search should still be supported if (SelectedProduct != null && string.IsNullOrEmpty(productNumberFilter)) { // assume SelectedProduct.Name correspond to ProductNumber field used when storing productNumberFilter = SelectedProduct.Name; } int pageIndex = PageIndex < 1 ? 1 : PageIndex; int pageSize = PageSize < 1 ? 20 : PageSize; var res = await _systemDatabaseService.QueryLockResultsAsync(start, end, pageIndex, pageSize, productNumberFilter, ScrewNumber); Results = new ObservableCollection(res.Items); TotalCount = res.TotalCount; } catch (Exception ex) { MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsSearching = false; } } private async Task ExecuteExportCommand() { try { IsSearching = true; // determine time range var start = StartDate.HasValue ? StartDate.Value.Date : DateTime.MinValue; var end = EndDate.HasValue ? EndDate.Value.Date.AddDays(1) : DateTime.MaxValue; string productNumberFilter = null; if (!string.IsNullOrWhiteSpace(ProductNumber)) productNumberFilter = ProductNumber.Trim(); if (SelectedProduct != null && string.IsNullOrEmpty(productNumberFilter)) { productNumberFilter = SelectedProduct.Name; } // fetch all matching records (not paged) var all = await _systemDatabaseService.QueryAllLockResultsAsync(start, end, productNumberFilter, ScrewNumber); if (all == null || !all.Any()) { MessageBox.Show("没有找到符合条件的记录。", "提示", MessageBoxButton.OK, MessageBoxImage.Information); return; } var saveFileDialog = new Microsoft.Win32.SaveFileDialog { FileName = $"LockResults_{DateTime.Now.ToString("yyyyMMddHHmmss")}.csv", DefaultExt = ".csv", Filter = "CSV Files (*.csv)|*.csv" }; var result = saveFileDialog.ShowDialog(); if (result != true) return; var path = saveFileDialog.FileName; // write in streaming manner to support large sets using (var sw = new StreamWriter(path, false, Encoding.UTF8)) { sw.WriteLine("Id,Number,ProductNumber,Timestamp,LockTurns,LockTorque,LockDuration,LockPassed,ResultDetails,ScrewdriverProgramNumber,Pressure,TargetTorque,TargetTurnsUpperLimit,TargetTurnsLowerLimit,TargetLockSpeed,StartPositionX,StartPositionY,StartPositionZ,EndPositionZ,LockDescentSpeed,FeederNumber,NozzleNumber,OperatorName,ProductImagePath"); foreach (var r in all) { var line = string.Join(",", EscapeCsv(r.Id.ToString()), EscapeCsv(r.Number.ToString()), EscapeCsv(r.ProductNumber), EscapeCsv(r.Timestamp.ToString("yyyy-MM-dd HH:mm:ss")), EscapeCsv(r.LockTurns.ToString()), EscapeCsv(r.LockTorque.ToString()), EscapeCsv(r.LockDuration.ToString()), EscapeCsv(r.LockPassed ? "1" : "0"), EscapeCsv(r.ResultDetails), EscapeCsv(r.ScrewdriverProgramNumber.ToString()), EscapeCsv(r.Pressure.ToString()), EscapeCsv(r.TargetTorque.ToString()), EscapeCsv(r.TargetTurnsUpperLimit.ToString()), EscapeCsv(r.TargetTurnsLowerLimit.ToString()), EscapeCsv(r.TargetLockSpeed.ToString()), EscapeCsv(r.StartPositionX.ToString()), EscapeCsv(r.StartPositionY.ToString()), EscapeCsv(r.StartPositionZ.ToString()), EscapeCsv(r.EndPositionZ.ToString()), EscapeCsv(r.LockDescentSpeed.ToString()), EscapeCsv(r.FeederNumber.ToString()), EscapeCsv(r.NozzleNumber), EscapeCsv(r.OperatorName), EscapeCsv(r.ProductImagePath) ); sw.WriteLine(line); } } } catch (Exception ex) { MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsSearching = false; } await Task.CompletedTask; } private string EscapeCsv(string s) { if (s == null) return ""; if (s.Contains(",") || s.Contains("\"") || s.Contains("\r") || s.Contains("\n")) { return "\"" + s.Replace("\"", "\"\"") + "\""; } return s; } } }