using System.Text.RegularExpressions; using MiniExcelLibs; namespace Oms.Application.Shared; /// /// 国家/地区 Excel 模板与导入解析。 /// public static class CountryImportHelper { public const string TemplateSheetName = "导入模板"; public const string TemplateFileName = "国家地区导入模板.xlsx"; private static readonly Regex CountryCodePattern = new("^[A-Za-z]{2}$", RegexOptions.Compiled); private static readonly Dictionary HeaderAliases = new(StringComparer.OrdinalIgnoreCase) { ["国家二字码"] = nameof(CountryImportRow.CountryCode), ["二字码"] = nameof(CountryImportRow.CountryCode), ["国家代码"] = nameof(CountryImportRow.CountryCode), ["CountryCode"] = nameof(CountryImportRow.CountryCode), ["countryCode"] = nameof(CountryImportRow.CountryCode), ["中文名"] = nameof(CountryImportRow.ChineseName), ["国家中文名"] = nameof(CountryImportRow.ChineseName), ["ChineseName"] = nameof(CountryImportRow.ChineseName), ["chineseName"] = nameof(CountryImportRow.ChineseName), ["英文名"] = nameof(CountryImportRow.EnglishName), ["国家英文名"] = nameof(CountryImportRow.EnglishName), ["EnglishName"] = nameof(CountryImportRow.EnglishName), ["englishName"] = nameof(CountryImportRow.EnglishName), }; public static async Task BuildTemplateBytesAsync() { var templateRows = new List> { new() { ["国家二字码"] = "CN", ["中文名"] = "中国", ["英文名"] = "China" }, new() { ["国家二字码"] = "US", ["中文名"] = "美国", ["英文名"] = "United States" } }; var sheets = new Dictionary { [TemplateSheetName] = templateRows }; await using var stream = new MemoryStream(); await MiniExcel.SaveAsAsync(stream, sheets); return stream.ToArray(); } public static async Task> ParseImportRowsAsync(Stream fileStream) { if (fileStream.CanSeek) { fileStream.Position = 0; } var sheetNames = MiniExcel.GetSheetNames(fileStream); var sheetName = sheetNames.FirstOrDefault(x => string.Equals(x, TemplateSheetName, StringComparison.OrdinalIgnoreCase)) ?? sheetNames.FirstOrDefault(); if (fileStream.CanSeek) { fileStream.Position = 0; } var rows = await MiniExcel.QueryAsync(fileStream, useHeaderRow: true, sheetName: sheetName); var result = new List<(int RowNumber, CountryImportRow Row)>(); var rowNumber = 1; foreach (var row in rows) { rowNumber++; if (row is not IDictionary rowDict || IsEmptyRow(rowDict)) { continue; } result.Add((rowNumber, ParseRow(rowDict))); } return result; } public static void Normalize(CountryImportRow row) { row.CountryCode = row.CountryCode?.Trim().ToUpperInvariant() ?? string.Empty; row.ChineseName = row.ChineseName?.Trim() ?? string.Empty; row.EnglishName = row.EnglishName?.Trim() ?? string.Empty; if (string.IsNullOrWhiteSpace(row.CountryCode)) { throw new InvalidOperationException("国家二字码不能为空"); } if (!CountryCodePattern.IsMatch(row.CountryCode)) { throw new InvalidOperationException("国家二字码须为 2 位字母,如 CN、US"); } if (string.IsNullOrWhiteSpace(row.ChineseName)) { throw new InvalidOperationException("中文名不能为空"); } if (row.ChineseName.Length > 128) { throw new InvalidOperationException("中文名不可超过 128 个字符"); } if (string.IsNullOrWhiteSpace(row.EnglishName)) { throw new InvalidOperationException("英文名不能为空"); } if (row.EnglishName.Length > 128) { throw new InvalidOperationException("英文名不可超过 128 个字符"); } } private static CountryImportRow ParseRow(IDictionary rowDict) { var mapped = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var (header, value) in rowDict) { if (string.IsNullOrWhiteSpace(header) || !HeaderAliases.TryGetValue(header.Trim(), out var field)) { continue; } mapped[field] = NormalizeCell(value); } return new CountryImportRow { CountryCode = mapped.GetValueOrDefault(nameof(CountryImportRow.CountryCode)) ?? string.Empty, ChineseName = mapped.GetValueOrDefault(nameof(CountryImportRow.ChineseName)) ?? string.Empty, EnglishName = mapped.GetValueOrDefault(nameof(CountryImportRow.EnglishName)) ?? string.Empty }; } private static string? NormalizeCell(object? value) { if (value is null) { return null; } var text = Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture)?.Trim(); return string.IsNullOrWhiteSpace(text) ? null : text; } private static bool IsEmptyRow(IDictionary rowDict) => rowDict.Values.All(value => string.IsNullOrWhiteSpace(NormalizeCell(value))); } /// /// 国家导入行。 /// public class CountryImportRow { public string CountryCode { get; set; } = string.Empty; public string ChineseName { get; set; } = string.Empty; public string EnglishName { get; set; } = string.Empty; }