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();
}