using FoodLabeling.Application.Contracts.Dtos.LabelTemplate; using FoodLabeling.Application.Services.DbModels; using SqlSugar; namespace FoodLabeling.Application.Helpers; /// /// 标签模板 Contents:汇总各控件展示名(编辑器 Element name / typeAdd 推导),逗号拼接。 /// public static class LabelTemplateContentsHelper { /// /// 保存时解析 Contents:优先入参 ,否则从 推导。 /// public static string ResolveForSave(string? inputContents, IReadOnlyList? elements) { var trimmed = inputContents?.Trim(); if (!string.IsNullOrWhiteSpace(trimmed)) { return trimmed; } return BuildFromElements(elements); } /// /// 从 elements 推导 Contents(去重、按 OrderNum 排序)。 /// public static string BuildFromElements(IReadOnlyList? elements) { if (elements is null || elements.Count == 0) { return string.Empty; } var names = CollectOrderedDistinctLabels( elements .OrderBy(e => e.OrderNum) .ThenBy(e => e.Id, StringComparer.Ordinal) .Select(e => ResolveElementContentsLabel(e.ElementName, e.TypeAdd, e.ElementType))); return names.Count > 0 ? string.Join(", ", names) : string.Empty; } /// /// 批量从 fl_label_template_element 推导 Contents。Key = 模板主键 Id。 /// public static async Task> ResolveContentsMapFromElementsAsync( ISqlSugarClient db, IReadOnlyList templateIds) { var result = new Dictionary(StringComparer.Ordinal); if (templateIds.Count == 0) { return result; } var rows = await db.Queryable() .Where(x => templateIds.Contains(x.TemplateId)) .Select(x => new ElementContentsRow { TemplateId = x.TemplateId, ElementName = x.ElementName, TypeAdd = x.TypeAdd, ElementType = x.ElementType, OrderNum = x.OrderNum, ElementKey = x.ElementKey }) .ToListAsync(); foreach (var group in rows.GroupBy(x => x.TemplateId)) { var names = CollectOrderedDistinctLabels( group .OrderBy(x => x.OrderNum) .ThenBy(x => x.ElementKey, StringComparer.Ordinal) .Select(x => ResolveElementContentsLabel(x.ElementName, x.TypeAdd, x.ElementType))); if (names.Count > 0) { result[group.Key] = string.Join(", ", names); } } return result; } /// /// 单控件 Contents 展示名:优先用户编辑的 Element name;旧 slug 则按 typeAdd 推导「控件名 (For Template)」。 /// public static string ResolveElementContentsLabel(string? elementName, string? typeAdd, string? elementType) { var derived = DeriveLabelFromTypeAdd(typeAdd, elementType); var name = elementName?.Trim(); if (string.IsNullOrWhiteSpace(name)) { return derived; } if (IsLegacySlugElementName(name, typeAdd, elementType)) { return string.IsNullOrWhiteSpace(derived) ? name : derived; } return name; } /// /// 从 typeAdd(如 template_Textlabel_Nutrition Facts)推导展示名。 /// public static string DeriveLabelFromTypeAdd(string? typeAdd, string? elementType) { var ta = typeAdd?.Trim(); if (!string.IsNullOrWhiteSpace(ta)) { var idx = ta.IndexOf('_'); if (idx >= 0 && idx < ta.Length - 1) { var prefix = ta[..idx].Trim().ToLowerInvariant(); var field = ta[(idx + 1)..].Trim(); if (!string.IsNullOrWhiteSpace(field)) { var category = MapPrefixToCategoryTitle(prefix); return category != null ? $"{field} ({category})" : field; } } } return MapElementTypeToDisplayLabel(elementType); } private static bool IsLegacySlugElementName(string name, string? typeAdd, string? elementType) { if (name.Contains(' ') || name.Contains('(')) { return false; } if (!LegacySlugPattern.IsMatch(name)) { return false; } var derived = DeriveLabelFromTypeAdd(typeAdd, elementType); if (string.IsNullOrWhiteSpace(derived)) { return true; } var field = ExtractTypeAddField(typeAdd); if (string.IsNullOrWhiteSpace(field)) { return true; } var slugField = SlugPaletteLabel(field); var slugName = name.Trim().ToLowerInvariant(); if (slugName == slugField) { return true; } if (slugName.StartsWith(slugField, StringComparison.Ordinal) && slugName.Length > slugField.Length && int.TryParse(slugName[slugField.Length..], out _)) { return true; } return false; } private static string? ExtractTypeAddField(string? typeAdd) { var ta = typeAdd?.Trim(); if (string.IsNullOrWhiteSpace(ta)) { return null; } var idx = ta.IndexOf('_'); return idx >= 0 && idx < ta.Length - 1 ? ta[(idx + 1)..].Trim() : null; } private static string SlugPaletteLabel(string label) => new string(label.Trim().ToLowerInvariant().Where(char.IsLetterOrDigit).ToArray()); private static string? MapPrefixToCategoryTitle(string prefix) => prefix switch { "template" => "For Template", "label" => "For Label", "auto" => "Entered Automatically", "print" => "Entered When Printing", _ => null }; private static string MapElementTypeToDisplayLabel(string? elementType) { var t = (elementType ?? string.Empty).Trim().ToUpperInvariant(); return t switch { "TEXT_PRODUCT" => "Label Name (For Label)", "TEXT_PRICE" => "Price (For Label)", "NUTRITION" or "NUTRITIONFACTS" => "Nutrition Facts (For Label)", "DURATION" => "Duration (For Label)", "DATE" => "Current Date (Entered Automatically)", "TIME" => "Current Time (Entered Automatically)", "BARCODE" => "Barcode (For Label)", "QRCODE" => "QR Code (For Label)", "BLANK" => "Blank Space (For Template)", "IMAGE" => "Image (For Label)", "TEXT_STATIC" => "Text (For Template)", _ => string.IsNullOrWhiteSpace(t) ? string.Empty : t.Replace('_', ' ') }; } private static List CollectOrderedDistinctLabels(IEnumerable labels) { var seen = new HashSet(StringComparer.OrdinalIgnoreCase); var names = new List(); foreach (var label in labels) { var text = label?.Trim(); if (string.IsNullOrWhiteSpace(text) || !seen.Add(text)) { continue; } names.Add(text); } return names; } private static readonly System.Text.RegularExpressions.Regex LegacySlugPattern = new("^[a-z0-9]+$", System.Text.RegularExpressions.RegexOptions.Compiled); private sealed class ElementContentsRow { public string TemplateId { get; init; } = string.Empty; public string ElementName { get; init; } = string.Empty; public string? TypeAdd { get; init; } public string ElementType { get; init; } = string.Empty; public int OrderNum { get; init; } public string ElementKey { get; init; } = string.Empty; } }