using FATrace.WPLApp.Core; using Prism.Commands; using System; using System.Collections.ObjectModel; using FreeSql; using FATrace.Model; using FATrace.WPLApp.Services; using FATrace.WPLApp.ModelDto; using System.Linq; using System.Threading.Tasks; using System.Windows; using Microsoft.Win32; using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; // 为避免与 System.Windows.HorizontalAlignment/VerticalAlignment 冲突,添加别名 using HAlign = NPOI.SS.UserModel.HorizontalAlignment; using VAlign = NPOI.SS.UserModel.VerticalAlignment; namespace FATrace.WPLApp.ViewModels { /// /// 原料生产使用信息查询 VM /// - 提供条件查询(时间范围、编码/名称/批次/扫码/操作人) /// - 支持使用 NPOI 导出为 Excel(.xlsx) /// 依赖注入:IFreeSql(数据库)、ILogService(日志) /// public class RawProUseViewModel : NavigationViewModel { private readonly IFreeSql _fsql; private readonly ILogService _log; private readonly CsvServices csvServices; public RawProUseViewModel(IFreeSql fsql, ILogService log, CsvServices csvServices) { _fsql = fsql; _log = log; this.csvServices = csvServices; Items = new ObservableCollection(); // 集合变化时动态刷新导出命令可用性 Items.CollectionChanged += (s, e) => { try { ExportCommand?.RaiseCanExecuteChanged(); } catch { } }; // 默认查询时间为今日 StartTime = DateTime.Today; EndTime = DateTime.Today.AddDays(1).AddSeconds(-1); SearchCommand = new DelegateCommand(async () => await SearchAsync(), () => !IsBusy) .ObservesProperty(() => IsBusy); ExportCommand = new DelegateCommand(ExportToExcel, () => !IsBusy && Items.Count > 0) .ObservesProperty(() => IsBusy) // Items 是集合引用,通常不会替换引用,这里通过 CollectionChanged 手动触发 ; ExportCSVCommand = new DelegateCommand(ExportToCsv, () => !IsBusy && Items.Count > 0) .ObservesProperty(() => IsBusy); ClearCommand = new DelegateCommand(ClearFilters, () => !IsBusy) .ObservesProperty(() => IsBusy); // 分页命令 FirstPageCommand = new DelegateCommand(async () => { if (PageIndex == 1) return; PageIndex = 1; await SearchAsync(); }, () => !IsBusy && PageIndex > 1) .ObservesProperty(() => IsBusy) .ObservesProperty(() => PageIndex); PrevPageCommand = new DelegateCommand(async () => { if (PageIndex <= 1) return; PageIndex -= 1; await SearchAsync(); }, () => !IsBusy && PageIndex > 1) .ObservesProperty(() => IsBusy) .ObservesProperty(() => PageIndex); NextPageCommand = new DelegateCommand(async () => { if (PageIndex >= TotalPages) return; PageIndex += 1; await SearchAsync(); }, () => !IsBusy && PageIndex < TotalPages) .ObservesProperty(() => IsBusy) .ObservesProperty(() => PageIndex) .ObservesProperty(() => TotalPages); LastPageCommand = new DelegateCommand(async () => { if (TotalPages <= 0) return; if (PageIndex == TotalPages) return; PageIndex = TotalPages; await SearchAsync(); }, () => !IsBusy && PageIndex < TotalPages) .ObservesProperty(() => IsBusy) .ObservesProperty(() => PageIndex) .ObservesProperty(() => TotalPages); } #region 查询条件属性 private string? _rawCode; /// /// 原料编号模糊匹配 /// public string? RawCode { get => _rawCode; set { _rawCode = value; RaisePropertyChanged(); } } private string? _rawName; /// /// 原料名称模糊匹配 /// public string? RawName { get => _rawName; set { _rawName = value; RaisePropertyChanged(); } } private string? _inBagCode; /// /// 内袋二维码模糊匹配 /// public string? InBagCode { get => _inBagCode; set { _inBagCode = value; RaisePropertyChanged(); } } private string? _boxCode; /// /// 外箱二维码模糊匹配 /// public string? BoxCode { get => _boxCode; set { _boxCode = value; RaisePropertyChanged(); } } private string? _batch; /// /// 批号模糊匹配 /// public string? Batch { get => _batch; set { _batch = value; RaisePropertyChanged(); } } private string? _opUser; /// /// 操作者模糊匹配 /// public string? OpUser { get => _opUser; set { _opUser = value; RaisePropertyChanged(); } } private string? _checkUser; /// /// 确认者模糊匹配 /// public string? CheckUser { get => _checkUser; set { _checkUser = value; RaisePropertyChanged(); } } private DateTime? _startTime; /// /// 查询起始时间(包含) /// public DateTime? StartTime { get => _startTime; set { _startTime = value; RaisePropertyChanged(); } } private DateTime? _endTime; /// /// 查询结束时间(包含) /// public DateTime? EndTime { get => _endTime; set { _endTime = value; RaisePropertyChanged(); } } #endregion #region 列表与状态 /// /// 查询结果数据集合(DTO) /// public ObservableCollection Items { get; } private bool _isBusy; /// /// 是否忙碌(查询/导出中) /// public bool IsBusy { get => _isBusy; set { _isBusy = value; RaisePropertyChanged(); } } private int _totalCount; /// /// 结果总数 /// public int TotalCount { get => _totalCount; set { _totalCount = value; RaisePropertyChanged(); } } // 分页属性 private int _pageIndex = 1; /// /// 当前页(从1开始) /// public int PageIndex { get => _pageIndex; set { _pageIndex = value < 1 ? 1 : value; RaisePropertyChanged(); } } private int _pageSize = 20; /// /// 每页数量 /// public int PageSize { get => _pageSize; set { var newSize = value <= 0 ? 20 : value; if (_pageSize != newSize) { _pageSize = newSize; RaisePropertyChanged(); // 页大小改变,重置到第1页并刷新 PageIndex = 1; if (!IsBusy) _ = SearchAsync(); } } } private int _totalPages; /// /// 总页数 /// public int TotalPages { get => _totalPages; set { _totalPages = value; RaisePropertyChanged(); } } #endregion #region 命令 public DelegateCommand SearchCommand { get; } public DelegateCommand ExportCommand { get; } public DelegateCommand ExportCSVCommand { get; } public DelegateCommand ClearCommand { get; } public DelegateCommand FirstPageCommand { get; } public DelegateCommand PrevPageCommand { get; } public DelegateCommand NextPageCommand { get; } public DelegateCommand LastPageCommand { get; } #endregion /// /// 清空查询条件 /// private void ClearFilters() { RawCode = RawName = InBagCode = BoxCode = Batch = OpUser = CheckUser = string.Empty; StartTime = DateTime.Today; EndTime = DateTime.Today.AddDays(1).AddSeconds(-1); } /// /// 执行查询(使用 FreeSql 构建查询语句,最终 ToList 获取数据) /// private async Task SearchAsync() { if (IsBusy) return; try { if (StartTime.HasValue && EndTime.HasValue && StartTime > EndTime) { MessageBox.Show("开始时间不能大于结束时间", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); return; } IsBusy = true; _log.Info("RawProUse 查询开始"); var data = await Task.Run(() => { var q = _fsql.Select(); // 计算安全的时间区间(EndTime 若无时分秒,则自动包含至该日23:59:59.9999999) DateTime? start = StartTime; DateTime? end = EndTime; if (start.HasValue) q = q.Where(a => a.WeightTime >= start.Value); if (end.HasValue) { var endInclusive = end.Value; if (endInclusive.TimeOfDay == TimeSpan.Zero) endInclusive = endInclusive.Date.AddDays(1).AddTicks(-1); q = q.Where(a => a.WeightTime <= endInclusive); } if (!string.IsNullOrWhiteSpace(RawCode)) q = q.Where(a => a.RawCode.Contains(RawCode)); if (!string.IsNullOrWhiteSpace(RawName)) q = q.Where(a => a.RawName.Contains(RawName)); if (!string.IsNullOrWhiteSpace(InBagCode)) q = q.Where(a => a.InBagCode.Contains(InBagCode)); if (!string.IsNullOrWhiteSpace(BoxCode)) q = q.Where(a => a.BoxCode.Contains(BoxCode)); if (!string.IsNullOrWhiteSpace(Batch)) q = q.Where(a => a.Batch.Contains(Batch)); if (!string.IsNullOrWhiteSpace(OpUser)) q = q.Where(a => a.OpUser.Contains(OpUser)); if (!string.IsNullOrWhiteSpace(CheckUser)) q = q.Where(a => a.CheckUser.Contains(CheckUser)); // 按时间倒序 q = q.OrderByDescending(a => a.WeightTime); // 计算总数并分页(Page 从1开始) var size = PageSize <= 0 ? 20 : PageSize; var page = PageIndex < 1 ? 1 : PageIndex; var list = q.Count(out var total) .Page(page, size) .ToList(a => new RawProUseDto { Id = a.Id, RawCode = a.RawCode, RawName = a.RawName, InBagCode = a.InBagCode, BoxCode = a.BoxCode, Batch = a.Batch, ShelfLife = a.ShelfLife, Weight = a.Weight, RemainWeight = a.RemainWeight, StockWeight = a.StockWeight, WeightTime = a.WeightTime, OpUser = a.OpUser, CheckUser = a.CheckUser, OutTime = a.OutTime, CreateTime = a.CreateTime }); // 若当前页超出总页数,则自动回退到最后一页 var pages = total <= 0 || size <= 0 ? 0 : (int)Math.Ceiling(total * 1.0 / size); if (pages > 0 && page > pages) { page = pages; list = q.Page(page, size).ToList(a => new RawProUseDto { Id = a.Id, RawCode = a.RawCode, RawName = a.RawName, InBagCode = a.InBagCode, BoxCode = a.BoxCode, Batch = a.Batch, ShelfLife = a.ShelfLife, Weight = a.Weight, RemainWeight = a.RemainWeight, StockWeight = a.StockWeight, WeightTime = a.WeightTime, OpUser = a.OpUser, CheckUser = a.CheckUser, OutTime = a.OutTime, CreateTime = a.CreateTime }); } return (items: list, total: (int)total, normalizedPage: page, totalPages: pages); }); Application.Current.Dispatcher.Invoke(() => { Items.Clear(); foreach (var it in data.items) Items.Add(it); TotalCount = data.total; TotalPages = data.totalPages; PageIndex = data.normalizedPage == 0 ? 1 : data.normalizedPage; }); _log.Info($"RawProUse 查询完成,记录数: {TotalCount}"); } catch (Exception ex) { _log.Error($"RawProUse 查询失败: {ex}"); MessageBox.Show($"查询失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsBusy = false; } } /// /// 使用 CsvServices 将当前界面数据导出为 CSV 文件(每条记录一个文件,命名为 InBagCode.csv) /// private void ExportToCsv() { if (IsBusy) return; if (Items.Count == 0) { System.Windows.MessageBox.Show("无可导出的数据", "提示", MessageBoxButton.OK, MessageBoxImage.Information); return; } try { IsBusy = true; var dr = "D:\\迅雷下载"; // 转换为 RawProUserCsvDto var all = Items.Select(it => new RawProUserCsvDto { RawCode = it.RawCode, RawName = it.RawName, InBagCode = it.InBagCode, BoxCode = it.BoxCode, Batch = it.Batch, ShelfLife = it.ShelfLife, Weight = it.Weight, DeliveryDate = it.WeightTime.ToString("yyyyMMdd"), RemainWeight = it.RemainWeight, StockWeight = it.StockWeight, WeightTime = it.WeightTime, OpUser = it.OpUser, CheckUser = it.CheckUser, OutTime = it.OutTime }).ToList(); // 过滤 InBagCode 为空的记录,避免导出失败 var valid = all.Where(x => !string.IsNullOrWhiteSpace(x.InBagCode)).ToList(); int skipped = all.Count - valid.Count; var svc = csvServices; var paths = svc.ExportMany(valid, dr, overwrite: true); _log.Info($"RawProUse CSV 导出完成: 目录={dr}, 成功={valid.Count}, 跳过={skipped}"); System.Windows.MessageBox.Show($"导出完成:成功 {valid.Count} 条,跳过 {skipped} 条。\n目录:{dr}", "提示", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { _log.Error($"RawProUse CSV 导出失败: {ex}"); System.Windows.MessageBox.Show($"导出失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsBusy = false; } } /// /// 导出当前查询结果为 Excel(.xlsx) /// private void ExportToExcel() { if (IsBusy) return; if (Items.Count == 0) { MessageBox.Show("无可导出的数据", "提示", MessageBoxButton.OK, MessageBoxImage.Information); return; } try { IsBusy = true; var sfd = new SaveFileDialog { Title = "导出为 Excel", Filter = "Excel 工作簿 (*.xlsx)|*.xlsx", FileName = $"RawProUse_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx" }; if (sfd.ShowDialog() != true) return; using IWorkbook workbook = new XSSFWorkbook(); var sheet = workbook.CreateSheet("原料使用"); // 表头 var header = sheet.CreateRow(0); string[] headers = new[] { "Id","原料编号","原料名称","内袋二维码","外箱二维码","批号","保质期(年)", "称重重量(g)","剩余重量(g)","入库总重量(g)","称重时间","操作者","确认者","出库时间","创建时间" }; // 表头样式:加粗与更大字号 var headerFont = workbook.CreateFont(); headerFont.IsBold = true; headerFont.FontHeightInPoints = 12; var headerStyle = workbook.CreateCellStyle(); headerStyle.SetFont(headerFont); headerStyle.Alignment = HAlign.Center; headerStyle.VerticalAlignment = VAlign.Center; header.HeightInPoints = 20f; for (int i = 0; i < headers.Length; i++) { var hc = header.CreateCell(i); hc.SetCellValue(headers[i]); hc.CellStyle = headerStyle; } // 日期格式 var dateStyle = workbook.CreateCellStyle(); var dataFormat = workbook.CreateDataFormat(); dateStyle.DataFormat = dataFormat.GetFormat("yyyy-mm-dd hh:mm:ss"); // 数据 for (int r = 0; r < Items.Count; r++) { var it = Items[r]; var row = sheet.CreateRow(r + 1); int c = 0; row.CreateCell(c++).SetCellValue((double)it.Id); row.CreateCell(c++).SetCellValue(it.RawCode ?? string.Empty); row.CreateCell(c++).SetCellValue(it.RawName ?? string.Empty); row.CreateCell(c++).SetCellValue(it.InBagCode ?? string.Empty); row.CreateCell(c++).SetCellValue(it.BoxCode ?? string.Empty); row.CreateCell(c++).SetCellValue(it.Batch ?? string.Empty); row.CreateCell(c++).SetCellValue(it.ShelfLife); row.CreateCell(c++).SetCellValue(it.Weight); row.CreateCell(c++).SetCellValue(it.RemainWeight); row.CreateCell(c++).SetCellValue(it.StockWeight); var cellWeightTime = row.CreateCell(c++); cellWeightTime.SetCellValue(it.WeightTime); cellWeightTime.CellStyle = dateStyle; row.CreateCell(c++).SetCellValue(it.OpUser ?? string.Empty); row.CreateCell(c++).SetCellValue(it.CheckUser ?? string.Empty); var cellOutTime = row.CreateCell(c++); cellOutTime.SetCellValue(it.OutTime); cellOutTime.CellStyle = dateStyle; var cellCreateTime = row.CreateCell(c++); cellCreateTime.SetCellValue(it.CreateTime); cellCreateTime.CellStyle = dateStyle; } // 自适应列宽(注意:大量数据时可能较慢) for (int i = 0; i < headers.Length; i++) { sheet.AutoSizeColumn(i); } using var fs = System.IO.File.OpenWrite(sfd.FileName); workbook.Write(fs); _log.Info($"RawProUse 导出完成: {sfd.FileName}"); MessageBox.Show("导出成功", "提示", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { _log.Error($"RawProUse 导出失败: {ex}"); MessageBox.Show($"导出失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsBusy = false; } } /// /// 导航进入时,可触发一次默认查询(可按需开启) /// /// public override async void OnNavigatedTo(Prism.Regions.NavigationContext navigationContext) { await SearchAsync(); } } }