diff --git a/yy-admin-master/YY.Admin.Services/Service/Print/NativePrintRenderService.cs b/yy-admin-master/YY.Admin.Services/Service/Print/NativePrintRenderService.cs index 62dc0af..00ec733 100644 --- a/yy-admin-master/YY.Admin.Services/Service/Print/NativePrintRenderService.cs +++ b/yy-admin-master/YY.Admin.Services/Service/Print/NativePrintRenderService.cs @@ -1017,14 +1017,18 @@ public static class NativePrintRenderService var colList = columns.OfType().ToList(); if (mergeColumnKeys.Length > 0) { - var byKey = colList.ToDictionary( - c => ReadAsString(c["key"]) ?? string.Empty, - c => ReadAsString(c["bindField"]) ?? ReadAsString(c["field"]) ?? string.Empty); + // 用 GroupBy 兜底,避免重复 key 抛异常;按 mergeColumnKeys 给定顺序输出对应 bindField/field + var byKey = colList + .GroupBy(c => ReadAsString(c["key"]) ?? string.Empty) + .ToDictionary( + g => g.Key, + g => ReadAsString(g.First()["bindField"]) ?? ReadAsString(g.First()["field"]) ?? string.Empty); return mergeColumnKeys .Select(k => byKey.TryGetValue(k, out var f) ? f : string.Empty) .Where(f => !string.IsNullOrEmpty(f)) .ToList(); } + // 兼容老的列级 mergeByValue 开关 return colList .Where(c => string.Equals(ReadAsString(c["mergeByValue"]), "true", StringComparison.OrdinalIgnoreCase)) .Select(c => ReadAsString(c["bindField"]) ?? ReadAsString(c["field"]) ?? string.Empty) diff --git a/yy-admin-master/YY.Admin/Views/Print/PrintPreviewWindow.xaml.cs b/yy-admin-master/YY.Admin/Views/Print/PrintPreviewWindow.xaml.cs index f26a1e7..93983c1 100644 --- a/yy-admin-master/YY.Admin/Views/Print/PrintPreviewWindow.xaml.cs +++ b/yy-admin-master/YY.Admin/Views/Print/PrintPreviewWindow.xaml.cs @@ -109,6 +109,8 @@ public partial class PrintPreviewWindow : HandyControl.Controls.Window /// /// 根据模板绑定字段生成参数 JSON(便于用户直接编辑并预览)。 + /// 与 web 端 nativeMockData.ts 保持一致:识别 mergeColumnKeys 让同组相邻行字段值相同, + /// 以便在预览中触发 rowSpan 合并显示。 /// private static string BuildMockParamJson(string templateJson) { @@ -121,6 +123,7 @@ public partial class PrintPreviewWindow : HandyControl.Controls.Window 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()) { @@ -130,28 +133,73 @@ public partial class PrintPreviewWindow : HandyControl.Controls.Window 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 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 columns.OfType()) + 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 * 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}" + "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}" }; - fields.Add(field); } rows.Add(row); + prevRow = row; } obj[source] = rows; }