Files
qhmes/yy-admin-master/YY.Admin/Views/Print/PrintPreviewWindow.xaml.cs

289 lines
12 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.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() => """
<!DOCTYPE html>
<html><head><meta charset="utf-8"/>
<style>
body { margin:0; background:#525659; display:flex;
align-items:center; justify-content:center; height:100vh;
font-family:"Microsoft YaHei",Arial,sans-serif; }
.card { background:#fff; border-radius:8px; padding:48px 64px;
text-align:center; box-shadow:0 6px 24px rgba(0,0,0,.4); }
.icon { font-size:48px; color:#ccc; margin-bottom:16px; }
.tip { font-size:15px; color:#888; }
</style></head>
<body>
<div class="card">
<div class="icon">📄</div>
<div class="tip"></div>
</div>
</body></html>
""";
/// <summary>
/// 左侧参数 JSON 重新渲染预览(与后端预览一致:用参数 JSON 驱动模板绑定字段)。
/// </summary>
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;
}
/// <summary>
/// 根据模板绑定字段生成参数 JSON便于用户直接编辑并预览
/// 与 web 端 nativeMockData.ts 保持一致:识别 mergeColumnKeys 让同组相邻行字段值相同,
/// 以便在预览中触发 rowSpan 合并显示。
/// </summary>
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<string>(StringComparer.OrdinalIgnoreCase);
var rng = new Random();
foreach (var el in elements.OfType<JsonObject>())
{
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<JsonObject>().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<string> 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("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;");
return "<html><head><meta charset=\"utf-8\"/><style>"
+ "body{margin:0;background:#525659;display:flex;align-items:center;"
+ "justify-content:center;height:100vh;font-family:'Microsoft YaHei',Arial,sans-serif;}"
+ ".card{background:#fff;border-radius:8px;padding:32px 48px;text-align:center;"
+ "box-shadow:0 6px 24px rgba(0,0,0,.4);max-width:560px;}"
+ "</style></head><body><div class=\"card\">"
+ "<div style=\"font-size:40px;margin-bottom:12px\">⚠️</div>"
+ "<div style=\"font-size:13px;color:#e74c3c;word-break:break-all;\">渲染失败:" + esc + "</div>"
+ "</div></body></html>";
}
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();
}