From 04dc9ca1ffc37540c8816e18a9ef18a86070da80 Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Tue, 6 Oct 2026 10:58:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=86=E5=8C=BA=E6=98=8E=E7=BB=86?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E6=A8=A1=E6=9D=BF=E5=8E=BB=E6=8E=89=E5=9B=BD?= =?UTF-8?q?=E5=AE=B6=E5=90=8D=E7=A7=B0=EF=BC=8C=E5=B9=B6=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=9E=9A=E4=B8=BE=E5=80=BC=20Sheet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 导入模板只保留国家代码;Sheet2 列出匹配维度/方式的编码与名称,导入时按代码补国家名称。 Co-authored-by: Cursor --- .../Shared/KpiAreaInfoImportHelper.cs | 58 +++++++++++++++++-- src/Oms.Tests/KpiAreaInfoImportHelperTests.cs | 58 +++++++++++++++++++ 2 files changed, 110 insertions(+), 6 deletions(-) diff --git a/src/Oms.Application/Shared/KpiAreaInfoImportHelper.cs b/src/Oms.Application/Shared/KpiAreaInfoImportHelper.cs index 2ebfabf..d61f58f 100644 --- a/src/Oms.Application/Shared/KpiAreaInfoImportHelper.cs +++ b/src/Oms.Application/Shared/KpiAreaInfoImportHelper.cs @@ -11,6 +11,9 @@ namespace Oms.Application.Shared; /// public static class KpiAreaInfoImportHelper { + public const string TemplateSheetName = "导入模板"; + public const string EnumSheetName = "枚举值"; + private static readonly Regex ChinesePattern = new(@"[\u4E00-\u9FA5]", RegexOptions.Compiled); private static readonly Dictionary HeaderAliases = new(StringComparer.OrdinalIgnoreCase) @@ -48,12 +51,11 @@ public static class KpiAreaInfoImportHelper public static async Task BuildTemplateBytesAsync() { - var rows = new List> + var templateRows = new List> { new() { ["国家代码"] = "US", - ["国家名称"] = "美国", ["匹配维度"] = "zip", ["匹配方式"] = "range", ["州/省"] = "", @@ -64,7 +66,6 @@ public static class KpiAreaInfoImportHelper new() { ["国家代码"] = "US", - ["国家名称"] = "美国", ["匹配维度"] = "zip", ["匹配方式"] = "prefix", ["州/省"] = "", @@ -75,7 +76,6 @@ public static class KpiAreaInfoImportHelper new() { ["国家代码"] = "US", - ["国家名称"] = "美国", ["匹配维度"] = "state", ["匹配方式"] = "exact", ["州/省"] = "CA", @@ -85,11 +85,44 @@ public static class KpiAreaInfoImportHelper } }; + var enumRows = new List> + { + new() { ["枚举类型"] = "匹配维度", ["编码"] = "country", ["名称"] = "仅国家" }, + new() { ["枚举类型"] = "匹配维度", ["编码"] = "zip", ["名称"] = "邮编" }, + new() { ["枚举类型"] = "匹配维度", ["编码"] = "state", ["名称"] = "州/省" }, + new() { ["枚举类型"] = "匹配维度", ["编码"] = "city", ["名称"] = "城市" }, + new() { ["枚举类型"] = "匹配方式", ["编码"] = "prefix", ["名称"] = "前缀" }, + new() { ["枚举类型"] = "匹配方式", ["编码"] = "exact", ["名称"] = "精确" }, + new() { ["枚举类型"] = "匹配方式", ["编码"] = "range", ["名称"] = "区间" }, + new() { ["枚举类型"] = "匹配方式", ["编码"] = "all", ["名称"] = "不限" } + }; + + var sheets = new Dictionary + { + [TemplateSheetName] = templateRows, + [EnumSheetName] = enumRows + }; + await using var stream = new MemoryStream(); - await MiniExcel.SaveAsAsync(stream, rows); + await MiniExcel.SaveAsAsync(stream, sheets); return stream.ToArray(); } + internal static string? ResolveImportSheetName(IEnumerable sheetNames) + { + var names = sheetNames.Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); + if (names.Count == 0) + { + return null; + } + + return names.FirstOrDefault(x => + string.Equals(x, TemplateSheetName, StringComparison.OrdinalIgnoreCase)) + ?? names.FirstOrDefault(x => + !string.Equals(x, EnumSheetName, StringComparison.OrdinalIgnoreCase)) + ?? names[0]; + } + public static string ResolveTemplateFileName(string? areaType) => areaType?.Trim().ToLowerInvariant() switch { @@ -101,7 +134,20 @@ public static class KpiAreaInfoImportHelper public static async Task> ParseImportRowsAsync(Stream fileStream) { - var rows = await MiniExcel.QueryAsync(fileStream, useHeaderRow: true); + if (fileStream.CanSeek) + { + fileStream.Position = 0; + } + + var sheetNames = MiniExcel.GetSheetNames(fileStream); + var sheetName = ResolveImportSheetName(sheetNames); + + if (fileStream.CanSeek) + { + fileStream.Position = 0; + } + + var rows = await MiniExcel.QueryAsync(fileStream, useHeaderRow: true, sheetName: sheetName); var result = new List<(int RowNumber, KpiAreaInfo Entity)>(); var rowNumber = 1; diff --git a/src/Oms.Tests/KpiAreaInfoImportHelperTests.cs b/src/Oms.Tests/KpiAreaInfoImportHelperTests.cs index 2f62e90..03cdf8c 100644 --- a/src/Oms.Tests/KpiAreaInfoImportHelperTests.cs +++ b/src/Oms.Tests/KpiAreaInfoImportHelperTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using MiniExcelLibs; using Oms.Application.Shared; using Oms.Domain.ChannelsAggregate.Entities; using Oms.Domain.SystemAggregate.Entities; @@ -7,6 +8,63 @@ namespace Oms.Tests; public class KpiAreaInfoImportHelperTests { + [Fact] + public async Task BuildTemplateBytesAsync_ShouldUseNamedSheetsWithoutCountryName() + { + var bytes = await KpiAreaInfoImportHelper.BuildTemplateBytesAsync(); + await using var stream = new MemoryStream(bytes); + + var sheetNames = MiniExcel.GetSheetNames(stream); + sheetNames.Should().Equal( + KpiAreaInfoImportHelper.TemplateSheetName, + KpiAreaInfoImportHelper.EnumSheetName); + + stream.Position = 0; + var templateRows = (await MiniExcel.QueryAsync( + stream, + useHeaderRow: true, + sheetName: KpiAreaInfoImportHelper.TemplateSheetName)) + .Cast>() + .ToList(); + templateRows.Should().NotBeEmpty(); + templateRows[0].Keys.Should().NotContain(key => key.Contains("国家名称")); + templateRows[0].Should().ContainKey("国家代码"); + templateRows[0].Should().ContainKey("匹配维度"); + templateRows[0].Should().ContainKey("匹配方式"); + + stream.Position = 0; + var enumRows = (await MiniExcel.QueryAsync( + stream, + useHeaderRow: true, + sheetName: KpiAreaInfoImportHelper.EnumSheetName)) + .Cast>() + .ToList(); + enumRows.Should().HaveCount(8); + enumRows.Should().Contain(row => + Convert.ToString(row["枚举类型"]) == "匹配维度" + && Convert.ToString(row["编码"]) == "zip" + && Convert.ToString(row["名称"]) == "邮编"); + enumRows.Should().Contain(row => + Convert.ToString(row["枚举类型"]) == "匹配方式" + && Convert.ToString(row["编码"]) == "range" + && Convert.ToString(row["名称"]) == "区间"); + } + + [Fact] + public async Task ParseImportRowsAsync_ShouldReadTemplateSheetOnly() + { + var bytes = await KpiAreaInfoImportHelper.BuildTemplateBytesAsync(); + await using var stream = new MemoryStream(bytes); + + var rows = await KpiAreaInfoImportHelper.ParseImportRowsAsync(stream); + + rows.Should().HaveCount(3); + rows[0].Entity.CountryCode.Should().Be("US"); + rows[0].Entity.CountryName.Should().BeNull(); + rows[0].Entity.MatchDim.Should().Be("zip"); + rows[0].Entity.MatchMode.Should().Be("range"); + } + [Fact] public void PrepareEntityForImport_ValidRow_ShouldFillCountryName() {