Files
FATrace/FATrace.WPLApp/ViewModels/RawProUseViewModel.cs
2025-11-26 16:46:48 +08:00

541 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <summary>
/// 原料生产使用信息查询 VM
/// - 提供条件查询(时间范围、编码/名称/批次/扫码/操作人)
/// - 支持使用 NPOI 导出为 Excel.xlsx
/// 依赖注入IFreeSql数据库、ILogService日志
/// </summary>
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<RawProUseDto>();
// 集合变化时动态刷新导出命令可用性
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;
/// <summary>
/// 原料编号模糊匹配
/// </summary>
public string? RawCode { get => _rawCode; set { _rawCode = value; RaisePropertyChanged(); } }
private string? _rawName;
/// <summary>
/// 原料名称模糊匹配
/// </summary>
public string? RawName { get => _rawName; set { _rawName = value; RaisePropertyChanged(); } }
private string? _inBagCode;
/// <summary>
/// 内袋二维码模糊匹配
/// </summary>
public string? InBagCode { get => _inBagCode; set { _inBagCode = value; RaisePropertyChanged(); } }
private string? _boxCode;
/// <summary>
/// 外箱二维码模糊匹配
/// </summary>
public string? BoxCode { get => _boxCode; set { _boxCode = value; RaisePropertyChanged(); } }
private string? _batch;
/// <summary>
/// 批号模糊匹配
/// </summary>
public string? Batch { get => _batch; set { _batch = value; RaisePropertyChanged(); } }
private string? _opUser;
/// <summary>
/// 操作者模糊匹配
/// </summary>
public string? OpUser { get => _opUser; set { _opUser = value; RaisePropertyChanged(); } }
private string? _checkUser;
/// <summary>
/// 确认者模糊匹配
/// </summary>
public string? CheckUser { get => _checkUser; set { _checkUser = value; RaisePropertyChanged(); } }
private DateTime? _startTime;
/// <summary>
/// 查询起始时间(包含)
/// </summary>
public DateTime? StartTime { get => _startTime; set { _startTime = value; RaisePropertyChanged(); } }
private DateTime? _endTime;
/// <summary>
/// 查询结束时间(包含)
/// </summary>
public DateTime? EndTime { get => _endTime; set { _endTime = value; RaisePropertyChanged(); } }
#endregion
#region
/// <summary>
/// 查询结果数据集合DTO
/// </summary>
public ObservableCollection<RawProUseDto> Items { get; }
private bool _isBusy;
/// <summary>
/// 是否忙碌(查询/导出中)
/// </summary>
public bool IsBusy { get => _isBusy; set { _isBusy = value; RaisePropertyChanged(); } }
private int _totalCount;
/// <summary>
/// 结果总数
/// </summary>
public int TotalCount { get => _totalCount; set { _totalCount = value; RaisePropertyChanged(); } }
// 分页属性
private int _pageIndex = 1;
/// <summary>
/// 当前页从1开始
/// </summary>
public int PageIndex
{
get => _pageIndex;
set { _pageIndex = value < 1 ? 1 : value; RaisePropertyChanged(); }
}
private int _pageSize = 20;
/// <summary>
/// 每页数量
/// </summary>
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;
/// <summary>
/// 总页数
/// </summary>
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
/// <summary>
/// 清空查询条件
/// </summary>
private void ClearFilters()
{
RawCode = RawName = InBagCode = BoxCode = Batch = OpUser = CheckUser = string.Empty;
StartTime = DateTime.Today;
EndTime = DateTime.Today.AddDays(1).AddSeconds(-1);
}
/// <summary>
/// 执行查询(使用 FreeSql 构建查询语句,最终 ToList 获取数据)
/// </summary>
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<RawProUse>();
// 计算安全的时间区间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;
}
}
/// <summary>
/// 使用 CsvServices 将当前界面数据导出为 CSV 文件(每条记录一个文件,命名为 InBagCode.csv
/// </summary>
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;
//}
}
/// <summary>
/// 导出当前查询结果为 Excel.xlsx
/// </summary>
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;
}
}
/// <summary>
/// 导航进入时,可触发一次默认查询(可按需开启)
/// </summary>
/// <param name="navigationContext"></param>
public override async void OnNavigatedTo(Prism.Regions.NavigationContext navigationContext)
{
await SearchAsync();
}
}
}