All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
Co-authored-by: Cursor <cursoragent@cursor.com>
180 lines
5.9 KiB
C#
180 lines
5.9 KiB
C#
using System.Text.RegularExpressions;
|
|
using MiniExcelLibs;
|
|
|
|
namespace Oms.Application.Shared;
|
|
|
|
/// <summary>
|
|
/// 国家/地区 Excel 模板与导入解析。
|
|
/// </summary>
|
|
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<string, string> 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<byte[]> BuildTemplateBytesAsync()
|
|
{
|
|
var templateRows = new List<Dictionary<string, object?>>
|
|
{
|
|
new()
|
|
{
|
|
["国家二字码"] = "CN",
|
|
["中文名"] = "中国",
|
|
["英文名"] = "China"
|
|
},
|
|
new()
|
|
{
|
|
["国家二字码"] = "US",
|
|
["中文名"] = "美国",
|
|
["英文名"] = "United States"
|
|
}
|
|
};
|
|
|
|
var sheets = new Dictionary<string, object>
|
|
{
|
|
[TemplateSheetName] = templateRows
|
|
};
|
|
|
|
await using var stream = new MemoryStream();
|
|
await MiniExcel.SaveAsAsync(stream, sheets);
|
|
return stream.ToArray();
|
|
}
|
|
|
|
public static async Task<List<(int RowNumber, CountryImportRow Row)>> 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<string, object> 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<string, object> rowDict)
|
|
{
|
|
var mapped = new Dictionary<string, string?>(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<string, object> rowDict) =>
|
|
rowDict.Values.All(value => string.IsNullOrWhiteSpace(NormalizeCell(value)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 国家导入行。
|
|
/// </summary>
|
|
public class CountryImportRow
|
|
{
|
|
public string CountryCode { get; set; } = string.Empty;
|
|
|
|
public string ChineseName { get; set; } = string.Empty;
|
|
|
|
public string EnglishName { get; set; } = string.Empty;
|
|
}
|