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;
public RawProUseViewModel(IFreeSql fsql, ILogService log)
{
_fsql = fsql;
_log = log;
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 手动触发
;
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 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;
}
}
///
/// 导出当前查询结果为 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();
}
}
}