Files
qhmes/yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialCardGenerateConfirmWindow.xaml.cs

390 lines
14 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 System.Collections.ObjectModel;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Windows;
using System.Windows.Controls.Primitives;
using YY.Admin.Core.Services;
using YY.Admin.ViewModels.RawMaterialEntry;
namespace YY.Admin.Views.RawMaterialEntry;
/// <summary>
/// 需显式实现 <see cref="INotifyPropertyChanged"/>,否则 WPF 绑定不会订阅 PropertyChanged
/// 界面会一直停留在属性初始值(例如 PrinterStatus 的「加载打印机中…」)。
/// </summary>
public partial class RawMaterialCardGenerateConfirmWindow : HandyControl.Controls.Window, INotifyPropertyChanged
{
private const int PrinterLoadTimeoutMs = 8000;
private const double DefaultLeftRatio = 0.7d;
private const double MinLeftRatio = 0.2d;
private const double MaxLeftRatio = 0.8d;
private static readonly JsonSerializerOptions LayoutJsonOpts = new() { WriteIndented = true };
private static string LayoutFilePath => Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"YY.Admin",
"raw-material-card-generate-confirm-layout.json");
public ObservableCollection<RawMaterialCardGeneratePlanRow> PlanItems { get; } = new();
public string HeaderText { get; }
public string TemplateText { get; }
public ObservableCollection<PrintDotPrinter> Printers { get; } = new();
private readonly Func<RawMaterialCardGeneratePlanRow, string> _previewHtmlBuilder;
private readonly IPrintDotService _printDotService;
private bool _webViewReady;
private bool _isRefreshingPrinters;
private bool _suppressPrinterSave;
private string? _preferredPrinterNameOnLoad;
private string _printerStatus = "加载打印机中...";
public string PrinterStatus
{
get => _printerStatus;
set
{
if (string.Equals(_printerStatus, value, StringComparison.Ordinal)) return;
_printerStatus = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PrinterStatus)));
}
}
private PrintDotPrinter? _selectedPrinter;
public PrintDotPrinter? SelectedPrinter
{
get => _selectedPrinter;
set
{
if (ReferenceEquals(_selectedPrinter, value)) return;
_selectedPrinter = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedPrinter)));
if (_suppressPrinterSave) return;
SaveLayout(dto => dto.SelectedPrinterName = value?.Name ?? string.Empty);
}
}
public string SelectedPrinterName => SelectedPrinter?.Name?.Trim() ?? string.Empty;
private RawMaterialCardGeneratePlanRow? _selectedPlanItem;
public RawMaterialCardGeneratePlanRow? SelectedPlanItem
{
get => _selectedPlanItem;
set
{
if (ReferenceEquals(_selectedPlanItem, value)) return;
_selectedPlanItem = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedPlanItem)));
}
}
public event PropertyChangedEventHandler? PropertyChanged;
public RawMaterialCardGenerateConfirmWindow(
IReadOnlyList<RawMaterialEntryOperationViewModel.RawMaterialCardGeneratePlanItem> planItems,
string templateName,
string templateCode,
IPrintDotService printDotService,
Func<RawMaterialCardGeneratePlanRow, string> previewHtmlBuilder)
{
InitializeComponent();
_printDotService = printDotService;
_previewHtmlBuilder = previewHtmlBuilder;
HeaderText = $"共 {planItems.Count} 张,左侧展示即将生成的原材料卡片,右侧展示业务关联打印模板预览。";
TemplateText = $"模板:{templateName}{templateCode}";
var idx = 0;
foreach (var item in planItems)
{
idx++;
PlanItems.Add(new RawMaterialCardGeneratePlanRow
{
Index = idx,
SourceRowNo = item.SourceRowNo,
DetailId = item.DetailId,
Card = item.Card
});
}
DataContext = this;
Loaded += OnLoadedAsync;
Closing += OnClosing;
}
private async void OnLoadedAsync(object? sender, RoutedEventArgs e)
{
var layout = LoadSavedLayout();
ApplyPaneRatio(layout.LeftPaneRatio ?? DefaultLeftRatio);
ApplySavedColumnWidths(layout.ColumnWidths);
_preferredPrinterNameOnLoad = layout.SelectedPrinterName;
await RefreshPrintersAsync(verbose: false);
try
{
await PreviewWebView.EnsureCoreWebView2Async();
_webViewReady = true;
if (SelectedPlanItem == null && PlanItems.Count > 0)
{
SelectedPlanItem = PlanItems[0];
}
RenderSelectedPreview();
}
catch
{
PreviewWebView.NavigateToString("<html><body style='font-family:Microsoft YaHei;padding:24px;'>模板预览加载失败</body></html>");
}
}
private async void RefreshPrintersButton_OnClick(object sender, RoutedEventArgs e)
{
await RefreshPrintersAsync(verbose: true);
}
private void MainSplitter_OnDragCompleted(object sender, DragCompletedEventArgs e)
{
SavePaneRatio(GetCurrentLeftRatio());
}
private double GetCurrentLeftRatio()
{
var left = LeftPaneCol.ActualWidth;
var right = RightPaneCol.ActualWidth;
var total = left + right;
if (total <= 0.1d) return DefaultLeftRatio;
return Math.Clamp(left / total, MinLeftRatio, MaxLeftRatio);
}
private void ApplyPaneRatio(double ratio)
{
var leftRatio = Math.Clamp(ratio, MinLeftRatio, MaxLeftRatio);
LeftPaneCol.Width = new GridLength(leftRatio, GridUnitType.Star);
RightPaneCol.Width = new GridLength(1d - leftRatio, GridUnitType.Star);
}
private static ConfirmWindowLayoutDto LoadSavedLayout()
{
try
{
if (!File.Exists(LayoutFilePath)) return new ConfirmWindowLayoutDto();
var json = File.ReadAllText(LayoutFilePath);
var dto = JsonSerializer.Deserialize<ConfirmWindowLayoutDto>(json, LayoutJsonOpts);
if (dto is null) return new ConfirmWindowLayoutDto();
if (dto.LeftPaneRatio is > 0 and < 1)
dto.LeftPaneRatio = Math.Clamp(dto.LeftPaneRatio.Value, MinLeftRatio, MaxLeftRatio);
return dto;
}
catch
{
// 布局缓存异常时使用默认比例,不影响主流程
}
return new ConfirmWindowLayoutDto();
}
private static void SavePaneRatio(double leftRatio)
{
SaveLayout(dto => { dto.LeftPaneRatio = Math.Clamp(leftRatio, MinLeftRatio, MaxLeftRatio); });
}
private static void SaveLayout(Action<ConfirmWindowLayoutDto> mutator)
{
try
{
var dir = Path.GetDirectoryName(LayoutFilePath);
if (!string.IsNullOrWhiteSpace(dir))
{
Directory.CreateDirectory(dir);
}
var dto = LoadSavedLayout();
mutator(dto);
File.WriteAllText(LayoutFilePath, JsonSerializer.Serialize(dto, LayoutJsonOpts));
}
catch
{
// 写入失败不阻断页面交互
}
}
private void ApplySavedColumnWidths(Dictionary<string, double>? columnWidths)
{
if (columnWidths is null || columnWidths.Count == 0) return;
for (var index = 0; index < PlanGrid.Columns.Count; index++)
{
var column = PlanGrid.Columns[index];
var key = GetColumnWidthKey(column, index);
if (!columnWidths.TryGetValue(key, out var cachedWidth) || cachedWidth <= 0) continue;
var minWidth = column.MinWidth > 0 ? column.MinWidth : 40d;
column.Width = new System.Windows.Controls.DataGridLength(
Math.Max(cachedWidth, minWidth),
System.Windows.Controls.DataGridLengthUnitType.Pixel);
}
}
private void SaveCurrentColumnWidths()
{
if (PlanGrid.Columns.Count == 0) return;
var widths = new Dictionary<string, double>(StringComparer.Ordinal);
for (var index = 0; index < PlanGrid.Columns.Count; index++)
{
var column = PlanGrid.Columns[index];
var width = column.ActualWidth;
if (double.IsNaN(width) || double.IsInfinity(width) || width <= 0) continue;
widths[GetColumnWidthKey(column, index)] = width;
}
if (widths.Count == 0) return;
SaveLayout(dto => dto.ColumnWidths = widths);
}
private static string GetColumnWidthKey(System.Windows.Controls.DataGridColumn column, int index)
{
var header = column.Header?.ToString();
if (string.IsNullOrWhiteSpace(header))
return $"col-{index.ToString(CultureInfo.InvariantCulture)}";
return $"col-{index.ToString(CultureInfo.InvariantCulture)}-{header.Trim()}";
}
private void OnClosing(object? sender, CancelEventArgs e)
{
SavePaneRatio(GetCurrentLeftRatio());
SaveCurrentColumnWidths();
}
private async Task RefreshPrintersAsync(bool verbose)
{
if (_isRefreshingPrinters) return;
_isRefreshingPrinters = true;
const string loadingText = "加载打印机中...";
PrinterStatus = verbose ? "刷新打印机中..." : loadingText;
try
{
using var cts = new CancellationTokenSource();
var fetchTask = _printDotService.GetPrintersAsync(cts.Token);
var timeoutTask = Task.Delay(PrinterLoadTimeoutMs);
var completedTask = await Task.WhenAny(fetchTask, timeoutTask);
if (!ReferenceEquals(completedTask, fetchTask))
{
cts.Cancel();
throw new TimeoutException("获取打印机列表超时");
}
var list = await fetchTask;
Printers.Clear();
foreach (var printer in list) Printers.Add(printer);
// 先更新状态,避免后续本地缓存逻辑异常导致“加载中”残留
PrinterStatus = list.Count > 0 ? $"共 {list.Count} 台打印机" : "未检测到打印机";
var preferred = _preferredPrinterNameOnLoad;
if (string.IsNullOrWhiteSpace(preferred))
{
preferred = SelectedPrinter?.Name;
}
var match = list.FirstOrDefault(p => string.Equals(p.Name, preferred, StringComparison.OrdinalIgnoreCase))
?? list.FirstOrDefault(p => p.IsDefault)
?? list.FirstOrDefault();
_suppressPrinterSave = true;
SelectedPrinter = match;
_suppressPrinterSave = false;
if (match is not null)
{
SaveLayout(dto => dto.SelectedPrinterName = match.Name);
}
_preferredPrinterNameOnLoad = null;
}
catch (OperationCanceledException)
{
Printers.Clear();
_suppressPrinterSave = true;
SelectedPrinter = null;
_suppressPrinterSave = false;
PrinterStatus = "加载打印机超时,请检查 PrintDot 后重试";
}
catch (TimeoutException)
{
Printers.Clear();
_suppressPrinterSave = true;
SelectedPrinter = null;
_suppressPrinterSave = false;
PrinterStatus = "加载打印机超时,请检查 PrintDot 后重试";
}
catch (Exception ex)
{
Printers.Clear();
_suppressPrinterSave = true;
SelectedPrinter = null;
_suppressPrinterSave = false;
PrinterStatus = verbose ? $"打印机连接失败:{ex.Message}" : "打印机连接失败";
}
finally
{
// 兜底:如果实际有打印机但状态仍是“加载中”,强制纠正状态文本
if (string.Equals(PrinterStatus, loadingText, StringComparison.Ordinal) && Printers.Count > 0)
{
PrinterStatus = $"共 {Printers.Count} 台打印机";
}
_isRefreshingPrinters = false;
}
}
private void PlanGrid_OnSelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
RenderSelectedPreview();
}
private void RenderSelectedPreview()
{
if (!_webViewReady) return;
if (SelectedPlanItem == null)
{
PreviewWebView.NavigateToString("<html><body style='font-family:Microsoft YaHei;padding:24px;'>请先选择左侧卡片记录</body></html>");
return;
}
try
{
var html = _previewHtmlBuilder.Invoke(SelectedPlanItem);
PreviewWebView.NavigateToString(html);
}
catch
{
PreviewWebView.NavigateToString("<html><body style='font-family:Microsoft YaHei;padding:24px;'>模板预览加载失败</body></html>");
}
}
private void CancelButton_OnClick(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
private void ConfirmButton_OnClick(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(SelectedPrinterName))
{
HandyControl.Controls.MessageBox.Warning("请先在当前弹窗选择打印机,再执行“生成并打印”。");
return;
}
DialogResult = true;
Close();
}
}
internal sealed class ConfirmWindowLayoutDto
{
public double? LeftPaneRatio { get; set; }
public Dictionary<string, double>? ColumnWidths { get; set; }
public string? SelectedPrinterName { get; set; }
}
public sealed class RawMaterialCardGeneratePlanRow
{
public int Index { get; init; }
public int SourceRowNo { get; init; }
public required string DetailId { get; init; }
public required YY.Admin.Core.Entity.MesXslRawMaterialCard Card { get; init; }
}