using System.Text.Json; using System.Text.Json.Nodes; using System.Text.RegularExpressions; using System.Windows; using YY.Admin.Core.Entity; using YY.Admin.Services.Service.Print; namespace YY.Admin.Views.Print; public partial class PrintPreviewWindow : HandyControl.Controls.Window { private readonly string _templateJson; public PrintPreviewWindow(PrintTemplate template, string? templateJson) { InitializeComponent(); _templateJson = templateJson ?? string.Empty; TbTemplateName.Text = template.TemplateName ?? "(未命名)"; TbTemplateCode.Text = $"编码:{template.TemplateCode} " + $"尺寸:{template.PaperWidthMm ?? 210}×{template.PaperHeightMm ?? 297} mm " + $"方向:{template.PaperOrientation ?? "纵向"}"; TbParamJson.Text = BuildMockParamJson(_templateJson); Loaded += async (_, _) => await LoadPreviewAsync(); } private async Task LoadPreviewAsync() { try { TbStatus.Text = "加载中…"; await WebView.EnsureCoreWebView2Async(); if (string.IsNullOrWhiteSpace(_templateJson) || _templateJson == "{}") { WebView.NavigateToString(BuildEmptyHtml()); TbStatus.Text = "尚未设计模板内容"; return; } await RenderCurrentParamJsonAsync(); } catch (Exception ex) { TbStatus.Text = $"预览失败:{ex.Message}"; } } private static string BuildEmptyHtml() => """
📄
该模板尚未设计内容
"""; /// /// 左侧参数 JSON 重新渲染预览(与后端预览一致:用参数 JSON 驱动模板绑定字段)。 /// private async Task RenderCurrentParamJsonAsync() { try { TbStatus.Text = "渲染中…"; JsonObject dataObj; var text = TbParamJson.Text?.Trim(); if (string.IsNullOrWhiteSpace(text)) { dataObj = new JsonObject(); } else { var node = JsonNode.Parse(text); if (node is not JsonObject obj) { WebView.NavigateToString(BuildErrorHtml("参数JSON必须是对象(JSON Object)")); TbStatus.Text = "参数JSON格式错误"; return; } dataObj = obj; } var html = NativePrintRenderService.RenderToHtml(_templateJson, dataObj); WebView.NavigateToString(html); TbStatus.Text = string.Empty; } catch (Exception ex) { WebView.NavigateToString(BuildErrorHtml(ex.Message)); TbStatus.Text = $"渲染失败:{ex.Message}"; } await Task.CompletedTask; } /// /// 根据模板绑定字段生成参数 JSON(便于用户直接编辑并预览)。 /// 与 web 端 nativeMockData.ts 保持一致:识别 mergeColumnKeys 让同组相邻行字段值相同, /// 以便在预览中触发 rowSpan 合并显示。 /// private static string BuildMockParamJson(string templateJson) { if (string.IsNullOrWhiteSpace(templateJson) || templateJson == "{}") return "{}"; try { var root = JsonNode.Parse(templateJson); var obj = new JsonObject(); var elements = root?["elements"]?.AsArray() ?? new JsonArray(); var fields = new HashSet(StringComparer.OrdinalIgnoreCase); var rng = new Random(); foreach (var el in elements.OfType()) { var type = (el["type"]?.ToString() ?? string.Empty).Trim(); if (type is "table" or "detailTable") { var source = (el["source"]?.ToString() ?? "mainTable").Trim(); if (!obj.ContainsKey(source)) { var columns = el["columns"]?.AsArray() ?? new JsonArray(); var colList = columns.OfType().ToList(); // 解析合并字段顺序:根据 mergeColumnKeys 按 column.key 映射到 bindField var mergeKeys = (el["mergeColumnKeys"]?.AsArray() ?? new JsonArray()) .Select(n => n?.ToString() ?? string.Empty) .Where(s => !string.IsNullOrEmpty(s)) .ToList(); var strictGrouping = !string.Equals( el["strictGrouping"]?.ToString() ?? "true", "false", StringComparison.OrdinalIgnoreCase); var mergeFieldOrder = mergeKeys .Select(k => colList.FirstOrDefault(c => string.Equals(c["key"]?.ToString() ?? string.Empty, k, StringComparison.Ordinal))) .Where(c => c != null) .Select(c => (c!["bindField"]?.ToString() ?? c!["field"]?.ToString() ?? string.Empty).Trim()) .Where(s => !string.IsNullOrEmpty(s)) .ToList(); var rows = new JsonArray(); JsonObject? prevRow = null; for (var i = 0; i < 8; i++) { var row = new JsonObject(); foreach (var col in colList) { var field = (col["bindField"]?.ToString() ?? col["field"]?.ToString() ?? string.Empty).Trim(); if (string.IsNullOrWhiteSpace(field)) continue; var contentType = (col["contentType"]?.ToString() ?? "text").Trim().ToLowerInvariant(); fields.Add(field); var mergeIndex = mergeFieldOrder.IndexOf(field); var enableMerge = mergeIndex >= 0; if (enableMerge) { // 父级合并字段在 strictGrouping 下必须与前一行一致,才允许沿用前一行值 var canFollowPrev = !strictGrouping || mergeIndex == 0 || (prevRow != null && mergeFieldOrder.Take(mergeIndex).All(parent => (prevRow[parent]?.ToString() ?? string.Empty) == (row[parent]?.ToString() ?? string.Empty))); if (i > 0 && prevRow != null && canFollowPrev && rng.NextDouble() < 0.5) { // 沿用前一行此字段值,从而触发合并 var prevVal = prevRow[field]; row[field] = prevVal != null ? JsonNode.Parse(prevVal.ToJsonString()) : (JsonNode?)$"{field}_合并组1"; } else { // 新合并组:随机 0-3 表示组编号 row[field] = $"{field}_合并组{rng.Next(1, 5)}"; } continue; } row[field] = contentType switch { "number" => (i + 1) * 123.45, "amount" => (i + 1) * 24567.89, "qrcode" => $"QR_{field}_{i + 1}", "barcode" => $"BAR_{field}_{i + 1}", "image" => $"https://picsum.photos/seed/{Uri.EscapeDataString(field + "_" + (i + 1))}/260/120", _ => $"{field}_示例值_{i + 1}" }; } rows.Add(row); prevRow = row; } obj[source] = rows; } } var bind = (el["bindField"]?.ToString() ?? string.Empty).Trim(); if (!string.IsNullOrWhiteSpace(bind)) fields.Add(bind); // 提取 text 中的 {{field}} 占位符(支持内嵌) var text = el["text"]?.ToString() ?? string.Empty; foreach (Match m in Regex.Matches(text, @"\{\{\s*([\w\.]+)\s*\}\}")) { var key = m.Groups[1].Value.Trim(); if (!string.IsNullOrWhiteSpace(key)) fields.Add(key); } CollectBindFields(el["cells"], fields); } foreach (var f in fields.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)) { if (f.Equals("pageNo", StringComparison.OrdinalIgnoreCase) || f.Equals("totalPages", StringComparison.OrdinalIgnoreCase)) continue; if (!obj.ContainsKey(f)) obj[f] = $"{f}_示例值"; } return obj.ToJsonString(new JsonSerializerOptions { WriteIndented = true }); } catch { return "{}"; } } private static void CollectBindFields(JsonNode? node, ISet fields) { if (node == null) return; if (node is JsonObject o) { if (o.TryGetPropertyValue("bindField", out var bindNode)) { var bind = bindNode?.ToString()?.Trim(); if (!string.IsNullOrWhiteSpace(bind)) fields.Add(bind); } foreach (var kv in o) CollectBindFields(kv.Value, fields); return; } if (node is JsonArray arr) { foreach (var it in arr) CollectBindFields(it, fields); } } private static string BuildErrorHtml(string message) { var esc = message.Replace("&", "&").Replace("<", "<").Replace(">", ">"); return "
" + "
⚠️
" + "
渲染失败:" + esc + "
" + "
"; } private async void RenderByParamJson_Click(object sender, RoutedEventArgs e) { await RenderCurrentParamJsonAsync(); } private async void GenerateMockJson_Click(object sender, RoutedEventArgs e) { TbParamJson.Text = BuildMockParamJson(_templateJson); await RenderCurrentParamJsonAsync(); } private void CloseButton_Click(object sender, RoutedEventArgs e) => Close(); }