新增打印模板管理功能,包含免密接口和实时通知机制,支持桌面端打印模板的查询和列表展示。更新相关控制器、服务和视图,优化用户体验并增强系统的实时数据同步能力。

This commit is contained in:
geht
2026-05-12 18:29:03 +08:00
parent f5ba828eff
commit fcedc66f7a
32 changed files with 2788 additions and 2 deletions

View File

@@ -0,0 +1,240 @@
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便于用户直接编辑并预览
/// </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);
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 rows = new JsonArray();
var columns = el["columns"]?.AsArray() ?? new JsonArray();
for (var i = 1; i <= 8; i++)
{
var row = new JsonObject();
foreach (var col in columns.OfType<JsonObject>())
{
var field = (col["bindField"]?.ToString() ?? col["field"]?.ToString() ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(field)) continue;
var contentType = (col["contentType"]?.ToString() ?? "text").Trim().ToLowerInvariant();
row[field] = contentType switch
{
"number" => i * 123.45,
"amount" => i * 24567.89,
"qrcode" => $"QR_{field}_{i}",
"barcode" => $"BAR_{field}_{i}",
"image" => $"https://picsum.photos/seed/{Uri.EscapeDataString(field + "_" + i)}/260/120",
_ => $"{field}_示例值_{i}"
};
fields.Add(field);
}
rows.Add(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();
}