新增原材料卡片生成逻辑,优化打印预览功能,支持卡片模板的加载和打印。重构相关方法以提升用户体验,确保生成的卡片数据有效,并处理打印异常情况。
This commit is contained in:
@@ -11,6 +11,7 @@ using YY.Admin.Core.Services;
|
|||||||
using YY.Admin.Infrastructure.Print;
|
using YY.Admin.Infrastructure.Print;
|
||||||
using YY.Admin.Services.Service;
|
using YY.Admin.Services.Service;
|
||||||
using YY.Admin.Services.Service.Print;
|
using YY.Admin.Services.Service.Print;
|
||||||
|
using YY.Admin.Views.RawMaterialEntry;
|
||||||
|
|
||||||
namespace YY.Admin.ViewModels.RawMaterialEntry;
|
namespace YY.Admin.ViewModels.RawMaterialEntry;
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
|||||||
private const int TodayListFetchSize = 5000;
|
private const int TodayListFetchSize = 5000;
|
||||||
private const string RawMaterialEntryBizCode = "1900000000000000530";
|
private const string RawMaterialEntryBizCode = "1900000000000000530";
|
||||||
private const string RawMaterialEntryTemplateCode = "MES_RAW_MATERIAL_ENTRY";
|
private const string RawMaterialEntryTemplateCode = "MES_RAW_MATERIAL_ENTRY";
|
||||||
|
private const string RawMaterialCardTemplateCode = "MES_RAW_MATERIAL_CARD";
|
||||||
private readonly IRawMaterialCardService _rawMaterialCardService;
|
private readonly IRawMaterialCardService _rawMaterialCardService;
|
||||||
private readonly IPrintDotService _printDotService;
|
private readonly IPrintDotService _printDotService;
|
||||||
private readonly IPrintBizTemplateBindService _printBizTemplateBindService;
|
private readonly IPrintBizTemplateBindService _printBizTemplateBindService;
|
||||||
@@ -151,6 +153,11 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
|||||||
private string _previewTemplateName = "原料入场记录";
|
private string _previewTemplateName = "原料入场记录";
|
||||||
private string _previewTemplateCode = RawMaterialEntryTemplateCode;
|
private string _previewTemplateCode = RawMaterialEntryTemplateCode;
|
||||||
private string _previewFieldMappingJson = "[]";
|
private string _previewFieldMappingJson = "[]";
|
||||||
|
private bool _rawMaterialCardTemplateLoaded;
|
||||||
|
private string _rawMaterialCardTemplateJson = string.Empty;
|
||||||
|
private string _rawMaterialCardTemplateName = "原材料卡片";
|
||||||
|
private string _rawMaterialCardTemplateCode = RawMaterialCardTemplateCode;
|
||||||
|
private string _rawMaterialCardFieldMappingJson = "[]";
|
||||||
|
|
||||||
private bool _isPrintPreviewExpanded = true;
|
private bool _isPrintPreviewExpanded = true;
|
||||||
/// <summary>右侧下方「入场标签打印预览」折叠面板是否展开。</summary>
|
/// <summary>右侧下方「入场标签打印预览」折叠面板是否展开。</summary>
|
||||||
@@ -694,18 +701,58 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 续号起点:已有卡片数 = Σ HasCard==true 行的 Portions;新增卡片从其后续编
|
||||||
|
var alreadyGenerated = SplitCodeDetails
|
||||||
|
.Where(d => d.HasCard)
|
||||||
|
.Sum(d => d.Portions ?? 0);
|
||||||
|
var plannedCards = BuildPlannedRawMaterialCards(Entry, pendingRows, alreadyGenerated + 1);
|
||||||
|
if (plannedCards.Count == 0)
|
||||||
|
{
|
||||||
|
HandyControl.Controls.MessageBox.Warning("未生成有效的原材料卡片预览数据,请检查拆码明细后重试。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await EnsureRawMaterialCardTemplateLoadedAsync())
|
||||||
|
{
|
||||||
|
HandyControl.Controls.MessageBox.Warning("未找到原材料卡片打印模板,请先同步“业务打印绑定/打印模板”后再试。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var confirmWindow = new RawMaterialCardGenerateConfirmWindow(
|
||||||
|
plannedCards,
|
||||||
|
_rawMaterialCardTemplateName,
|
||||||
|
_rawMaterialCardTemplateCode,
|
||||||
|
_printDotService,
|
||||||
|
row =>
|
||||||
|
{
|
||||||
|
var matched = plannedCards.FirstOrDefault(p =>
|
||||||
|
string.Equals(p.DetailId, row.DetailId, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& string.Equals(p.Card.Barcode, row.Card.Barcode, StringComparison.OrdinalIgnoreCase));
|
||||||
|
var target = matched ?? plannedCards[Math.Clamp(row.Index - 1, 0, plannedCards.Count - 1)];
|
||||||
|
return BuildRawMaterialCardPreviewHtml(target);
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Owner = Application.Current.MainWindow,
|
||||||
|
WindowStartupLocation = WindowStartupLocation.CenterOwner
|
||||||
|
};
|
||||||
|
var confirmed = confirmWindow.ShowDialog() == true;
|
||||||
|
if (!confirmed) return;
|
||||||
|
var selectedPrinterName = confirmWindow.SelectedPrinterName;
|
||||||
|
if (string.IsNullOrWhiteSpace(selectedPrinterName))
|
||||||
|
{
|
||||||
|
HandyControl.Controls.MessageBox.Warning("未选择打印机,已取消打印。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
BeginActionBusy("生成中...");
|
BeginActionBusy("生成中...");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IsLoading = true;
|
IsLoading = true;
|
||||||
// 续号起点:已有卡片数 = Σ HasCard==true 行的 Portions;新增卡片从其后续编
|
|
||||||
var alreadyGenerated = SplitCodeDetails
|
|
||||||
.Where(d => d.HasCard)
|
|
||||||
.Sum(d => d.Portions ?? 0);
|
|
||||||
var globalIndex = alreadyGenerated + 1;
|
var globalIndex = alreadyGenerated + 1;
|
||||||
var baseBarcode = Entry.Barcode ?? "";
|
var baseBarcode = Entry.Barcode ?? "";
|
||||||
var failCount = 0;
|
var failCount = 0;
|
||||||
var newCardCount = 0;
|
var newCardCount = 0;
|
||||||
|
var generatedCards = new List<MesXslRawMaterialCard>();
|
||||||
// 按行收集成功标记:只要该行所有份都加卡成功,行 HasCard=true;
|
// 按行收集成功标记:只要该行所有份都加卡成功,行 HasCard=true;
|
||||||
// 中途任一份失败则保留 HasCard=false,下次再点「生成」时会重试该行
|
// 中途任一份失败则保留 HasCard=false,下次再点「生成」时会重试该行
|
||||||
var rowSuccessMap = new Dictionary<RawMaterialSplitDetailItem, bool>();
|
var rowSuccessMap = new Dictionary<RawMaterialSplitDetailItem, bool>();
|
||||||
@@ -740,7 +787,11 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
|||||||
TenantId = Entry.TenantId
|
TenantId = Entry.TenantId
|
||||||
};
|
};
|
||||||
var ok = await _rawMaterialCardService.AddAsync(card);
|
var ok = await _rawMaterialCardService.AddAsync(card);
|
||||||
if (ok) newCardCount++;
|
if (ok)
|
||||||
|
{
|
||||||
|
newCardCount++;
|
||||||
|
generatedCards.Add(card);
|
||||||
|
}
|
||||||
else { failCount++; rowAllOk = false; }
|
else { failCount++; rowAllOk = false; }
|
||||||
globalIndex++;
|
globalIndex++;
|
||||||
}
|
}
|
||||||
@@ -769,8 +820,17 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
|||||||
RaisePropertyChanged(nameof(CanGenerateCards));
|
RaisePropertyChanged(nameof(CanGenerateCards));
|
||||||
RaisePropertyChanged(nameof(CanResplit));
|
RaisePropertyChanged(nameof(CanResplit));
|
||||||
|
|
||||||
|
if (newCardCount > 0 && generatedCards.Count > 0)
|
||||||
|
{
|
||||||
|
var printError = await PrintGeneratedRawMaterialCardsAsync(selectedPrinterName, generatedCards);
|
||||||
|
if (!string.IsNullOrWhiteSpace(printError))
|
||||||
|
{
|
||||||
|
HandyControl.Controls.MessageBox.Warning($"卡片已生成 {newCardCount} 张,但打印存在异常:{printError}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (failCount == 0)
|
if (failCount == 0)
|
||||||
HandyControl.Controls.MessageBox.Success($"已生成 {newCardCount} 张原材料卡片(累计 {alreadyGenerated + newCardCount} 张),打印状态已更新为「已打印」!");
|
HandyControl.Controls.MessageBox.Success($"已生成并打印 {newCardCount} 张原材料卡片(累计 {alreadyGenerated + newCardCount} 张),打印状态已更新为「已打印」!");
|
||||||
else
|
else
|
||||||
HandyControl.Controls.MessageBox.Warning($"本次共尝试生成 {newCardCount + failCount} 张,成功 {newCardCount} 张,失败 {failCount} 张。失败的行未标记为「已打印」,可检查网络后再次点击「生成原材料卡片」重试。");
|
HandyControl.Controls.MessageBox.Warning($"本次共尝试生成 {newCardCount + failCount} 张,成功 {newCardCount} 张,失败 {failCount} 张。失败的行未标记为「已打印」,可检查网络后再次点击「生成原材料卡片」重试。");
|
||||||
}
|
}
|
||||||
@@ -785,6 +845,215 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<RawMaterialCardGeneratePlanItem> BuildPlannedRawMaterialCards(
|
||||||
|
MesXslRawMaterialEntry entry,
|
||||||
|
IReadOnlyList<RawMaterialSplitDetailItem> pendingRows,
|
||||||
|
int startIndex)
|
||||||
|
{
|
||||||
|
var list = new List<RawMaterialCardGeneratePlanItem>();
|
||||||
|
var cursor = startIndex;
|
||||||
|
var baseBarcode = entry.Barcode ?? string.Empty;
|
||||||
|
foreach (var detail in pendingRows)
|
||||||
|
{
|
||||||
|
var portions = detail.Portions ?? 0;
|
||||||
|
for (var i = 0; i < portions; i++)
|
||||||
|
{
|
||||||
|
var card = BuildRawMaterialCardFromDetail(entry, detail, baseBarcode + cursor.ToString("D3"));
|
||||||
|
list.Add(new RawMaterialCardGeneratePlanItem
|
||||||
|
{
|
||||||
|
Card = card,
|
||||||
|
DetailId = detail.Id,
|
||||||
|
SourceRowNo = SplitCodeDetails.IndexOf(detail) + 1
|
||||||
|
});
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MesXslRawMaterialCard BuildRawMaterialCardFromDetail(
|
||||||
|
MesXslRawMaterialEntry entry,
|
||||||
|
RawMaterialSplitDetailItem detail,
|
||||||
|
string barcode)
|
||||||
|
{
|
||||||
|
return new MesXslRawMaterialCard
|
||||||
|
{
|
||||||
|
SplitDetailId = detail.Id,
|
||||||
|
Barcode = barcode,
|
||||||
|
BatchNo = entry.BatchNo,
|
||||||
|
EntryDate = entry.EntryTime?.Date ?? DateTime.Today,
|
||||||
|
MaterialId = entry.MaterialId,
|
||||||
|
MaterialName = entry.MaterialName,
|
||||||
|
SupplierId = entry.SupplierId,
|
||||||
|
SupplierName = entry.SupplierName,
|
||||||
|
ManufacturerMaterialName = entry.ManufacturerMaterialName,
|
||||||
|
ShelfLife = entry.ShelfLife,
|
||||||
|
TotalWeight = detail.PortionWeight.HasValue ? (decimal?)detail.PortionWeight.Value : null,
|
||||||
|
RemainingWeight = detail.PortionWeight.HasValue ? (decimal?)detail.PortionWeight.Value : null,
|
||||||
|
RemainingQuantity = detail.PortionPackages,
|
||||||
|
WarehouseArea = detail.WarehouseLocation,
|
||||||
|
UnloadOperator = entry.UnloadOperator,
|
||||||
|
Status = "1",
|
||||||
|
TestResult = "0",
|
||||||
|
PriorityPickup = "0",
|
||||||
|
TenantId = entry.TenantId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> EnsureRawMaterialCardTemplateLoadedAsync()
|
||||||
|
{
|
||||||
|
if (_rawMaterialCardTemplateLoaded && !string.IsNullOrWhiteSpace(_rawMaterialCardTemplateJson))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bindList = _printBizTemplateBindService.GetCached();
|
||||||
|
if (bindList.Count == 0)
|
||||||
|
{
|
||||||
|
try { bindList = await _printBizTemplateBindService.ListAsync().ConfigureAwait(false); } catch { /* 忽略 */ }
|
||||||
|
}
|
||||||
|
var bind = bindList.FirstOrDefault(x => string.Equals(x.TemplateCode, RawMaterialCardTemplateCode, StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? bindList.FirstOrDefault(x => (x.BizName ?? string.Empty).Contains("原材料卡片", StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (bind == null) return false;
|
||||||
|
|
||||||
|
_rawMaterialCardFieldMappingJson = string.IsNullOrWhiteSpace(bind.FieldMappingJson) ? "[]" : bind.FieldMappingJson!;
|
||||||
|
_rawMaterialCardTemplateCode = string.IsNullOrWhiteSpace(bind.TemplateCode) ? RawMaterialCardTemplateCode : bind.TemplateCode!;
|
||||||
|
|
||||||
|
var templates = _printTemplateService.GetCached();
|
||||||
|
if (templates.Count == 0)
|
||||||
|
{
|
||||||
|
try { templates = await _printTemplateService.ListAsync().ConfigureAwait(false); } catch { /* 忽略 */ }
|
||||||
|
}
|
||||||
|
var tpl = templates.FirstOrDefault(t => !string.IsNullOrWhiteSpace(bind.TemplateId) && string.Equals(t.Id, bind.TemplateId, StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? templates.FirstOrDefault(t => string.Equals(t.TemplateCode, _rawMaterialCardTemplateCode, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (tpl == null || string.IsNullOrWhiteSpace(tpl.TemplateJson)) return false;
|
||||||
|
|
||||||
|
_rawMaterialCardTemplateJson = tpl.TemplateJson!;
|
||||||
|
_rawMaterialCardTemplateName = string.IsNullOrWhiteSpace(tpl.TemplateName) ? "原材料卡片" : tpl.TemplateName!;
|
||||||
|
_rawMaterialCardTemplateLoaded = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildRawMaterialCardPreviewHtml(RawMaterialCardGeneratePlanItem planItem)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = BuildRawMaterialCardPrintData(planItem.Card);
|
||||||
|
return NativePrintRenderService.RenderToHtml(_rawMaterialCardTemplateJson, data, enableScreenAutoFit: false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return BuildPrintPreviewErrorHtml($"模板预览失败:{ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonObject BuildRawMaterialCardPrintData(MesXslRawMaterialCard card)
|
||||||
|
{
|
||||||
|
JsonObject printData = new();
|
||||||
|
JsonNode? bizRoot = JsonSerializer.SerializeToNode(card, PreviewSnapshotJsonOpts);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mappingNode = JsonNode.Parse(string.IsNullOrWhiteSpace(_rawMaterialCardFieldMappingJson) ? "[]" : _rawMaterialCardFieldMappingJson) as JsonArray;
|
||||||
|
if (mappingNode != null)
|
||||||
|
{
|
||||||
|
foreach (var rule in mappingNode)
|
||||||
|
{
|
||||||
|
if (rule is not JsonObject obj) continue;
|
||||||
|
var templateField = obj["templateField"]?.GetValue<string>()?.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(templateField)) continue;
|
||||||
|
var bizField = obj["bizField"]?.GetValue<string>()?.Trim();
|
||||||
|
JsonNode? val = string.IsNullOrWhiteSpace(bizField)
|
||||||
|
? JsonValue.Create(string.Empty)
|
||||||
|
: ResolvePath(bizRoot, bizField!);
|
||||||
|
var normalized = NormalizePreviewNodeValue(val);
|
||||||
|
SetPath(printData, templateField!, normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 映射失败时降级为模板字段补空,不中断后续流程
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var root = JsonNode.Parse(_rawMaterialCardTemplateJson);
|
||||||
|
var fields = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
CollectTemplateBindFields(root, fields);
|
||||||
|
foreach (var key in fields)
|
||||||
|
{
|
||||||
|
if (!HasPath(printData, key))
|
||||||
|
{
|
||||||
|
SetPath(printData, key, JsonValue.Create(string.Empty));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 忽略模板结构异常
|
||||||
|
}
|
||||||
|
return printData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string?> PrintGeneratedRawMaterialCardsAsync(string printerName, IReadOnlyList<MesXslRawMaterialCard> cards)
|
||||||
|
{
|
||||||
|
if (cards.Count == 0) return null;
|
||||||
|
if (string.IsNullOrWhiteSpace(printerName)) return "未选择打印机";
|
||||||
|
if (!await EnsureRawMaterialCardTemplateLoadedAsync()) return "未找到原材料卡片打印模板";
|
||||||
|
|
||||||
|
var tpl = BuildPrintTemplateForRawMaterialCard(_rawMaterialCardTemplateJson);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var idx = 0;
|
||||||
|
foreach (var card in cards)
|
||||||
|
{
|
||||||
|
idx++;
|
||||||
|
BeginActionBusy($"打印中({idx}/{cards.Count})...");
|
||||||
|
var data = BuildRawMaterialCardPrintData(card);
|
||||||
|
var html = NativePrintRenderService.RenderToHtml(_rawMaterialCardTemplateJson, data);
|
||||||
|
var pdfBase64 = await HtmlToPdfRenderer.RenderAsync(
|
||||||
|
html,
|
||||||
|
tpl.PaperWidthMm ?? 210d,
|
||||||
|
tpl.PaperHeightMm ?? 297d);
|
||||||
|
var jobName = string.IsNullOrWhiteSpace(card.Barcode) ? "原材料卡片" : $"原材料卡片-{card.Barcode}";
|
||||||
|
await _printDotService.PrintAsync(printerName, pdfBase64, jobName, 1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return ex.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PrintTemplate BuildPrintTemplateForRawMaterialCard(string? templateJson)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(templateJson) || templateJson == "{}")
|
||||||
|
return new PrintTemplate { TemplateName = "原材料卡片", TemplateCode = RawMaterialCardTemplateCode };
|
||||||
|
var root = JsonDocument.Parse(templateJson).RootElement;
|
||||||
|
double w = 210, h = 297;
|
||||||
|
if (root.TryGetProperty("page", out var page))
|
||||||
|
{
|
||||||
|
if (page.TryGetProperty("width", out var wEl)) w = wEl.GetDouble();
|
||||||
|
if (page.TryGetProperty("height", out var hEl)) h = hEl.GetDouble();
|
||||||
|
}
|
||||||
|
return new PrintTemplate
|
||||||
|
{
|
||||||
|
TemplateName = "原材料卡片",
|
||||||
|
TemplateCode = RawMaterialCardTemplateCode,
|
||||||
|
PaperWidthMm = w,
|
||||||
|
PaperHeightMm = h,
|
||||||
|
PaperOrientation = w > h ? "横向" : "纵向",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new PrintTemplate { TemplateName = "原材料卡片", TemplateCode = RawMaterialCardTemplateCode };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void BeginActionBusy(string text)
|
private void BeginActionBusy(string text)
|
||||||
{
|
{
|
||||||
ActionBusyText = string.IsNullOrWhiteSpace(text) ? "处理中..." : text;
|
ActionBusyText = string.IsNullOrWhiteSpace(text) ? "处理中..." : text;
|
||||||
@@ -1152,4 +1421,11 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
|||||||
public double? ExpandedWidth { get; set; }
|
public double? ExpandedWidth { get; set; }
|
||||||
public bool? IsExpanded { get; set; }
|
public bool? IsExpanded { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class RawMaterialCardGeneratePlanItem
|
||||||
|
{
|
||||||
|
public required MesXslRawMaterialCard Card { get; init; }
|
||||||
|
public required string DetailId { get; init; }
|
||||||
|
public int SourceRowNo { get; init; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
<hc:Window x:Class="YY.Admin.Views.RawMaterialEntry.RawMaterialCardGenerateConfirmWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||||
|
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
|
||||||
|
Width="1416"
|
||||||
|
Height="864"
|
||||||
|
MinWidth="1176"
|
||||||
|
MinHeight="744"
|
||||||
|
Title="生成并打印原材料卡片"
|
||||||
|
ResizeMode="CanResize">
|
||||||
|
<Grid Margin="16">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0" Margin="0,0,0,10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel Grid.Column="0">
|
||||||
|
<TextBlock Text="即将生成的原材料卡片"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
<TextBlock Text="{Binding HeaderText}"
|
||||||
|
Margin="0,4,0,0"
|
||||||
|
FontSize="12"
|
||||||
|
Foreground="#8C8C8C"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Column="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="打印机:"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,0,6,0"/>
|
||||||
|
<ComboBox Width="300"
|
||||||
|
Height="34"
|
||||||
|
ItemsSource="{Binding Printers}"
|
||||||
|
SelectedItem="{Binding SelectedPrinter, Mode=TwoWay}"
|
||||||
|
DisplayMemberPath="Name"
|
||||||
|
VerticalContentAlignment="Center"/>
|
||||||
|
<Button Content="刷新打印机"
|
||||||
|
Height="34"
|
||||||
|
Margin="8,0,8,0"
|
||||||
|
Padding="12,0"
|
||||||
|
Click="RefreshPrintersButton_OnClick"/>
|
||||||
|
<TextBlock Text="{Binding PrinterStatus}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="#595959"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition x:Name="LeftPaneCol" Width="7*"/>
|
||||||
|
<ColumnDefinition Width="10"/>
|
||||||
|
<ColumnDefinition x:Name="RightPaneCol" Width="3*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Border Grid.Column="0"
|
||||||
|
BorderBrush="#E5E6EB"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="4"
|
||||||
|
Padding="0"
|
||||||
|
Background="#FFFFFF">
|
||||||
|
<DataGrid x:Name="PlanGrid"
|
||||||
|
ItemsSource="{Binding PlanItems}"
|
||||||
|
SelectedItem="{Binding SelectedPlanItem, Mode=TwoWay}"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
HeadersVisibility="Column"
|
||||||
|
CanUserResizeColumns="True"
|
||||||
|
CanUserReorderColumns="False"
|
||||||
|
CanUserSortColumns="False"
|
||||||
|
CanUserAddRows="False"
|
||||||
|
CanUserDeleteRows="False"
|
||||||
|
IsReadOnly="True"
|
||||||
|
GridLinesVisibility="None"
|
||||||
|
HorizontalGridLinesBrush="Transparent"
|
||||||
|
VerticalGridLinesBrush="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
RowHeaderWidth="0"
|
||||||
|
SelectionMode="Single"
|
||||||
|
SelectionUnit="FullRow"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||||
|
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||||
|
SelectionChanged="PlanGrid_OnSelectionChanged">
|
||||||
|
<DataGrid.Resources>
|
||||||
|
<Style x:Key="CenteredCellTextStyle" TargetType="TextBlock">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="TextAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Resources>
|
||||||
|
<DataGrid.ColumnHeaderStyle>
|
||||||
|
<Style TargetType="DataGridColumnHeader">
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
|
<Setter Property="MinHeight" Value="36"/>
|
||||||
|
<Setter Property="Padding" Value="8,6"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.ColumnHeaderStyle>
|
||||||
|
<DataGrid.RowStyle>
|
||||||
|
<Style TargetType="DataGridRow">
|
||||||
|
<Setter Property="Margin" Value="0,0,0,3"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||||
|
<Setter Property="Background" Value="#FFFFFF"/>
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.RowStyle>
|
||||||
|
<DataGrid.CellStyle>
|
||||||
|
<Style TargetType="DataGridCell">
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||||
|
<Setter Property="Padding" Value="8,10"/>
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="TextBlock.TextAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.CellStyle>
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="#" Binding="{Binding Index}" Width="70" MinWidth="60" ElementStyle="{StaticResource CenteredCellTextStyle}"/>
|
||||||
|
<DataGridTextColumn Header="条码" Binding="{Binding Card.Barcode}" Width="220" MinWidth="170" ElementStyle="{StaticResource CenteredCellTextStyle}"/>
|
||||||
|
<DataGridTextColumn Header="来源行" Binding="{Binding SourceRowNo}" Width="90" MinWidth="70" ElementStyle="{StaticResource CenteredCellTextStyle}"/>
|
||||||
|
<DataGridTextColumn Header="物料名称" Binding="{Binding Card.MaterialName}" Width="220" MinWidth="160" ElementStyle="{StaticResource CenteredCellTextStyle}"/>
|
||||||
|
<DataGridTextColumn Header="批次号" Binding="{Binding Card.BatchNo}" Width="220" MinWidth="160" ElementStyle="{StaticResource CenteredCellTextStyle}"/>
|
||||||
|
<DataGridTextColumn Header="库位" Binding="{Binding Card.WarehouseArea}" Width="150" MinWidth="100" ElementStyle="{StaticResource CenteredCellTextStyle}"/>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<GridSplitter Grid.Column="1"
|
||||||
|
Width="10"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Background="Transparent"
|
||||||
|
ResizeBehavior="PreviousAndNext"
|
||||||
|
Cursor="SizeWE"
|
||||||
|
DragCompleted="MainSplitter_OnDragCompleted"/>
|
||||||
|
|
||||||
|
<Border Grid.Column="2"
|
||||||
|
BorderBrush="#E5E6EB"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="4"
|
||||||
|
Background="#FFFFFF">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Grid.Row="0" Margin="10,8,10,8">
|
||||||
|
<TextBlock Text="打印模板预览" FontSize="14" FontWeight="SemiBold"/>
|
||||||
|
<TextBlock Text="{Binding TemplateText}" Margin="0,4,0,0" FontSize="12" Foreground="#8C8C8C"/>
|
||||||
|
</StackPanel>
|
||||||
|
<wv2:WebView2 x:Name="PreviewWebView" Grid.Row="1" DefaultBackgroundColor="#FFFFFFFF"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="2"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Margin="0,12,0,0">
|
||||||
|
<Button Content="取消"
|
||||||
|
Width="92"
|
||||||
|
Height="34"
|
||||||
|
Margin="0,0,10,0"
|
||||||
|
Click="CancelButton_OnClick"
|
||||||
|
Style="{StaticResource ButtonDefault}"/>
|
||||||
|
<Button Content="生成并打印"
|
||||||
|
Width="120"
|
||||||
|
Height="34"
|
||||||
|
Click="ConfirmButton_OnClick"
|
||||||
|
Style="{StaticResource ButtonPrimary}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</hc:Window>
|
||||||
@@ -0,0 +1,389 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user