2025-12-25 11:20:56 +08:00
|
|
|
|
using CsvHelper;
|
|
|
|
|
|
using CsvHelper.Configuration;
|
|
|
|
|
|
using OfficeOpenXml;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Service;
|
|
|
|
|
|
|
|
|
|
|
|
public interface IImportService
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 导入支付宝账单
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
Task<(bool ok, string message)> ImportAlipayAsync(MemoryStream file, string fileExtension);
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 导入微信账单
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
Task<(bool ok, string message)> ImportWeChatAsync(MemoryStream file, string fileExtension);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class ImportService(
|
|
|
|
|
|
ILogger<ImportService> logger,
|
|
|
|
|
|
ITransactionRecordRepository transactionRecordRepository
|
|
|
|
|
|
) : IImportService
|
|
|
|
|
|
{
|
|
|
|
|
|
public async Task<(bool ok, string message)> ImportAlipayAsync(MemoryStream file, string fileExtension)
|
|
|
|
|
|
{
|
|
|
|
|
|
var content = await ParseAsync(file, fileExtension);
|
|
|
|
|
|
logger.LogInformation("转换后的支付宝账单数据行数: {RowCount}", content.Length);
|
|
|
|
|
|
|
|
|
|
|
|
if (content.Length == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("支付宝账单文件解析后无数据行");
|
|
|
|
|
|
return (false, "支付宝账单文件解析后无数据行");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var addTransactionRecords = new List<TransactionRecord>();
|
|
|
|
|
|
var updateTransactionRecords = new List<TransactionRecord>();
|
|
|
|
|
|
foreach (var row in content)
|
|
|
|
|
|
{
|
|
|
|
|
|
var importNo = row.ContainsKey("交易号") ? row["交易号"] : string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(importNo))
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("跳过无交易号的记录: {Row}", row);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var existingRecord = await transactionRecordRepository.ExistsByImportNoAsync(importNo, "支付宝");
|
|
|
|
|
|
|
|
|
|
|
|
if (existingRecord != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
existingRecord.Reason = GetReason(row);
|
|
|
|
|
|
existingRecord.Amount = GetDecimalValue(row, "金额(元)");
|
|
|
|
|
|
existingRecord.RefundAmount = GetDecimalValue(row, "成功退款(元)");
|
|
|
|
|
|
existingRecord.OccurredAt = GetDateTimeValue(row, "交易创建时间");
|
|
|
|
|
|
existingRecord.Type = GetTransactionType(row, "收/支");
|
|
|
|
|
|
updateTransactionRecords.Add(existingRecord);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var transactionRecord = new TransactionRecord
|
|
|
|
|
|
{
|
|
|
|
|
|
Reason = GetReason(row),
|
|
|
|
|
|
Amount = GetDecimalValue(row, "金额(元)"),
|
|
|
|
|
|
RefundAmount = GetDecimalValue(row, "成功退款(元)"),
|
|
|
|
|
|
Balance = 0,
|
|
|
|
|
|
OccurredAt = GetDateTimeValue(row, "交易创建时间"),
|
|
|
|
|
|
Type = GetTransactionType(row, "收/支"),
|
|
|
|
|
|
ImportNo = importNo,
|
|
|
|
|
|
ImportFrom = "支付宝"
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
addTransactionRecords.Add(transactionRecord);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (addTransactionRecords.Count == 0 && updateTransactionRecords.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("未找到可导入或更新的支付宝交易记录");
|
|
|
|
|
|
return (false, "未找到可导入或更新的支付宝交易记录");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var message = new StringBuilder();
|
|
|
|
|
|
if (addTransactionRecords.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (await transactionRecordRepository.AddRangeAsync(addTransactionRecords))
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("成功导入支付宝交易记录数: {Count}", addTransactionRecords.Count);
|
|
|
|
|
|
message.AppendLine($"成功导入支付宝交易记录数: {addTransactionRecords.Count}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (updateTransactionRecords.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (await transactionRecordRepository.UpdateRangeAsync(updateTransactionRecords))
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("成功更新支付宝交易记录数: {Count}", updateTransactionRecords.Count);
|
|
|
|
|
|
message.AppendLine($"成功更新支付宝交易记录数: {updateTransactionRecords.Count}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (true, message.ToString());
|
|
|
|
|
|
|
|
|
|
|
|
string GetReason(IDictionary<string, string> row)
|
|
|
|
|
|
{
|
|
|
|
|
|
var reason = string.Empty;
|
|
|
|
|
|
if (row.ContainsKey("交易对方") && !string.IsNullOrWhiteSpace(row["交易对方"]))
|
|
|
|
|
|
{
|
|
|
|
|
|
reason += row["交易对方"];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (row.ContainsKey("商品名称") && !string.IsNullOrWhiteSpace(row["商品名称"]))
|
|
|
|
|
|
{
|
|
|
|
|
|
reason += row["商品名称"];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return reason;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
decimal GetDecimalValue(IDictionary<string, string> row, string key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (row.ContainsKey(key) && decimal.TryParse(row[key], out var value))
|
|
|
|
|
|
{
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return 0m;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DateTime GetDateTimeValue(IDictionary<string, string> row, string key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!row.ContainsKey(key))
|
|
|
|
|
|
{
|
|
|
|
|
|
return DateTime.MinValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var format in DateTimeFormats)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (DateTime.TryParseExact(
|
|
|
|
|
|
row[key],
|
|
|
|
|
|
format,
|
|
|
|
|
|
CultureInfo.InvariantCulture,
|
|
|
|
|
|
DateTimeStyles.None,
|
|
|
|
|
|
out var value))
|
|
|
|
|
|
{
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (DateTime.TryParse(row[key], out var value2))
|
|
|
|
|
|
{
|
|
|
|
|
|
return value2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return DateTime.MinValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TransactionType GetTransactionType(IDictionary<string, string> row, string key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!row.ContainsKey(key))
|
|
|
|
|
|
{
|
|
|
|
|
|
return TransactionType.None;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var typeStr = row[key];
|
|
|
|
|
|
|
|
|
|
|
|
return typeStr switch
|
|
|
|
|
|
{
|
|
|
|
|
|
"支出" => TransactionType.Expense,
|
|
|
|
|
|
"收入" => TransactionType.Income,
|
|
|
|
|
|
_ => TransactionType.None
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<(bool ok, string message)> ImportWeChatAsync(MemoryStream file, string fileExtension)
|
|
|
|
|
|
{
|
|
|
|
|
|
var content = await ParseAsync(file, fileExtension);
|
|
|
|
|
|
logger.LogInformation("转换后的微信账单数据行数: {RowCount}", content.Length);
|
|
|
|
|
|
|
|
|
|
|
|
if (content.Length == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("微信账单文件解析后无数据行");
|
|
|
|
|
|
return (false, "微信账单文件解析后无数据行");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var addTransactionRecords = new List<TransactionRecord>();
|
|
|
|
|
|
var updateTransactionRecords = new List<TransactionRecord>();
|
|
|
|
|
|
foreach (var row in content)
|
|
|
|
|
|
{
|
|
|
|
|
|
var importNo = row.ContainsKey("交易单号") ? row["交易单号"] : string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(importNo))
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("跳过无交易单号的记录: {Row}", row);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var existingRecord = await transactionRecordRepository.ExistsByImportNoAsync(importNo, "微信");
|
|
|
|
|
|
|
|
|
|
|
|
if (existingRecord != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
existingRecord.Reason = GetReason(row);
|
|
|
|
|
|
existingRecord.Amount = GetAmountValue(row, "金额(元)");
|
|
|
|
|
|
existingRecord.OccurredAt = GetDateTimeValue(row, "交易时间");
|
|
|
|
|
|
existingRecord.Type = GetTransactionType(row, "收/支");
|
|
|
|
|
|
existingRecord.RefundAmount = GetRefundAmountValue(row);
|
|
|
|
|
|
updateTransactionRecords.Add(existingRecord);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var transactionRecord = new TransactionRecord
|
|
|
|
|
|
{
|
|
|
|
|
|
Reason = GetReason(row),
|
|
|
|
|
|
Amount = GetAmountValue(row, "金额(元)"),
|
|
|
|
|
|
RefundAmount = GetRefundAmountValue(row),
|
|
|
|
|
|
Balance = 0,
|
|
|
|
|
|
OccurredAt = GetDateTimeValue(row, "交易时间"),
|
|
|
|
|
|
Type = GetTransactionType(row, "收/支"),
|
|
|
|
|
|
ImportNo = importNo,
|
|
|
|
|
|
ImportFrom = "微信"
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
addTransactionRecords.Add(transactionRecord);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (addTransactionRecords.Count == 0 && updateTransactionRecords.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("未找到可导入或更新的微信交易记录");
|
|
|
|
|
|
return (false, "未找到可导入或更新的微信交易记录");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var message = new StringBuilder();
|
|
|
|
|
|
if (addTransactionRecords.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (await transactionRecordRepository.AddRangeAsync(addTransactionRecords))
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("成功导入微信交易记录数: {Count}", addTransactionRecords.Count);
|
|
|
|
|
|
message.AppendLine($"成功导入微信交易记录数: {addTransactionRecords.Count}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (updateTransactionRecords.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (await transactionRecordRepository.UpdateRangeAsync(updateTransactionRecords))
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("成功更新微信交易记录数: {Count}", updateTransactionRecords.Count);
|
|
|
|
|
|
message.AppendLine($"成功更新微信交易记录数: {updateTransactionRecords.Count}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (true, message.ToString());
|
|
|
|
|
|
|
|
|
|
|
|
string GetReason(IDictionary<string, string> row)
|
|
|
|
|
|
{
|
|
|
|
|
|
var reason = string.Empty;
|
|
|
|
|
|
if (row.ContainsKey("交易类型") && !string.IsNullOrWhiteSpace(row["交易类型"]))
|
|
|
|
|
|
{
|
|
|
|
|
|
reason += row["交易类型"];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (row.ContainsKey("交易对方") && !string.IsNullOrWhiteSpace(row["交易对方"]))
|
|
|
|
|
|
{
|
|
|
|
|
|
reason += row["交易对方"];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (row.ContainsKey("商品") && !string.IsNullOrWhiteSpace(row["商品"]))
|
|
|
|
|
|
{
|
|
|
|
|
|
reason += row["商品"];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return reason;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
decimal GetAmountValue(IDictionary<string, string> row, string key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (row.ContainsKey(key) && decimal.TryParse(row[key].TrimStart('¥').TrimStart('¥'), out var value))
|
|
|
|
|
|
{
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return 0m;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DateTime GetDateTimeValue(IDictionary<string, string> row, string key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!row.ContainsKey(key))
|
|
|
|
|
|
{
|
|
|
|
|
|
return DateTime.MinValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var format in DateTimeFormats)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (DateTime.TryParseExact(
|
|
|
|
|
|
row[key],
|
|
|
|
|
|
format,
|
|
|
|
|
|
CultureInfo.InvariantCulture,
|
|
|
|
|
|
DateTimeStyles.None,
|
|
|
|
|
|
out var value))
|
|
|
|
|
|
{
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (DateTime.TryParse(row[key], out var value2))
|
|
|
|
|
|
{
|
|
|
|
|
|
return value2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return DateTime.MinValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TransactionType GetTransactionType(IDictionary<string, string> row, string key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!row.ContainsKey(key))
|
|
|
|
|
|
{
|
|
|
|
|
|
return TransactionType.None;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var typeStr = row[key];
|
|
|
|
|
|
|
|
|
|
|
|
return typeStr switch
|
|
|
|
|
|
{
|
|
|
|
|
|
"支出" => TransactionType.Expense,
|
|
|
|
|
|
"收入" => TransactionType.Income,
|
|
|
|
|
|
_ => TransactionType.None
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
decimal GetRefundAmountValue(IDictionary<string, string> row)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!row.ContainsKey("当前状态"))
|
|
|
|
|
|
{
|
|
|
|
|
|
return 0m;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var status = row["当前状态"];
|
|
|
|
|
|
|
|
|
|
|
|
if (!status.Contains("退款"))
|
|
|
|
|
|
{
|
|
|
|
|
|
return 0m;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用正则表达式提取退款金额
|
2025-12-27 11:50:12 +08:00
|
|
|
|
var regex = new Regex(@"¥(-?\d+(\.\d+)?)");
|
2025-12-25 11:20:56 +08:00
|
|
|
|
var match = regex.Match(status);
|
|
|
|
|
|
if (match.Success && decimal.TryParse(match.Groups[1].Value, out var refundAmount))
|
|
|
|
|
|
{
|
|
|
|
|
|
return refundAmount;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return 0m;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<IDictionary<string, string>[]> ParseAsync(MemoryStream file, string fileExtension)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (fileExtension == ".csv")
|
|
|
|
|
|
{
|
|
|
|
|
|
return await ParseCsvAsync(file);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (fileExtension == ".xlsx" || fileExtension == ".xls")
|
|
|
|
|
|
{
|
|
|
|
|
|
return await ParseExcelAsync(file);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotSupportedException("不支持的文件格式");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<IDictionary<string, string>[]> ParseCsvAsync(MemoryStream file)
|
|
|
|
|
|
{
|
|
|
|
|
|
file.Position = 0;
|
|
|
|
|
|
using var reader = new StreamReader(file, Encoding.UTF8);
|
|
|
|
|
|
|
|
|
|
|
|
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
|
|
|
|
|
|
{
|
|
|
|
|
|
HasHeaderRecord = true,
|
|
|
|
|
|
TrimOptions = TrimOptions.Trim
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
using var csv = new CsvReader(reader, config);
|
|
|
|
|
|
|
|
|
|
|
|
// 读取表头
|
|
|
|
|
|
await csv.ReadAsync();
|
|
|
|
|
|
csv.ReadHeader();
|
|
|
|
|
|
var headers = csv.HeaderRecord;
|
|
|
|
|
|
|
|
|
|
|
|
if (headers == null || headers.Length == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Array.Empty<IDictionary<string, string>>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var result = new List<IDictionary<string, string>>();
|
|
|
|
|
|
|
|
|
|
|
|
// 读取数据行
|
|
|
|
|
|
while (await csv.ReadAsync())
|
|
|
|
|
|
{
|
|
|
|
|
|
var row = new Dictionary<string, string>();
|
|
|
|
|
|
foreach (var header in headers)
|
|
|
|
|
|
{
|
|
|
|
|
|
row[header] = csv.GetField(header) ?? string.Empty;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result.Add(row);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result.ToArray();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<IDictionary<string, string>[]> ParseExcelAsync(MemoryStream file)
|
|
|
|
|
|
{
|
|
|
|
|
|
file.Position = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 设置 EPPlus 许可证上下文
|
|
|
|
|
|
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
|
|
|
|
|
|
|
|
|
|
|
|
using var package = new ExcelPackage(file);
|
|
|
|
|
|
var worksheet = package.Workbook.Worksheets.FirstOrDefault();
|
|
|
|
|
|
|
|
|
|
|
|
if (worksheet == null || worksheet.Dimension == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Array.Empty<IDictionary<string, string>>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var rowCount = worksheet.Dimension.End.Row;
|
|
|
|
|
|
var colCount = worksheet.Dimension.End.Column;
|
|
|
|
|
|
|
|
|
|
|
|
if (rowCount < 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Array.Empty<IDictionary<string, string>>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 读取表头(第一行)
|
|
|
|
|
|
var headers = new List<string>();
|
|
|
|
|
|
for (int col = 1; col <= colCount; col++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var header = worksheet.Cells[1, col].Text?.Trim() ?? string.Empty;
|
|
|
|
|
|
headers.Add(header);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var result = new List<IDictionary<string, string>>();
|
|
|
|
|
|
|
|
|
|
|
|
// 读取数据行(从第二行开始)
|
|
|
|
|
|
for (int row = 2; row <= rowCount; row++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var rowData = new Dictionary<string, string>();
|
|
|
|
|
|
for (int col = 1; col <= colCount; col++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var header = headers[col - 1];
|
|
|
|
|
|
var value = worksheet.Cells[row, col].Text?.Trim() ?? string.Empty;
|
|
|
|
|
|
rowData[header] = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result.Add(rowData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return await Task.FromResult(result.ToArray());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static string[] DateTimeFormats =
|
|
|
|
|
|
[
|
|
|
|
|
|
"yyyy-MM-dd",
|
|
|
|
|
|
"yyyy-MM-dd HH",
|
|
|
|
|
|
"yyyy-MM-dd HH:mm",
|
|
|
|
|
|
"yyyy-MM-dd HH:mm:ss",
|
|
|
|
|
|
"yyyy-M-d",
|
|
|
|
|
|
"yyyy-M-d HH:mm",
|
|
|
|
|
|
"yyyy-M-d HH:mm:ss",
|
|
|
|
|
|
"yyyy/MM/dd",
|
|
|
|
|
|
"yyyy/MM/dd HH",
|
|
|
|
|
|
"yyyy/MM/dd HH:mm",
|
|
|
|
|
|
"yyyy/MM/dd HH:mm:ss",
|
|
|
|
|
|
"yyyy/M/d",
|
|
|
|
|
|
"yyyy/M/d HH:mm",
|
|
|
|
|
|
"yyyy/M/d HH:mm:ss",
|
|
|
|
|
|
"MM/dd/yyyy",
|
|
|
|
|
|
"MM/dd/yyyy HH",
|
|
|
|
|
|
"MM/dd/yyyy HH:mm",
|
|
|
|
|
|
"MM/dd/yyyy HH:mm:ss",
|
|
|
|
|
|
"M/d/yyyy",
|
|
|
|
|
|
"M/d/yyyy HH:mm",
|
|
|
|
|
|
"M/d/yyyy HH:mm:ss",
|
|
|
|
|
|
"MM/dd/yy",
|
|
|
|
|
|
"M/d/yy H:mm",
|
|
|
|
|
|
"MM/dd/yy HH",
|
|
|
|
|
|
"MM/dd/yy HH:mm",
|
|
|
|
|
|
"MM/dd/yy HH:mm:ss",
|
|
|
|
|
|
"M/d/yy",
|
|
|
|
|
|
"M/d/yy HH",
|
|
|
|
|
|
"M/d/yy HH:mm",
|
|
|
|
|
|
"M/d/yy HH:mm:ss",
|
|
|
|
|
|
"yyyyMMdd",
|
|
|
|
|
|
"yyyyMMddHH",
|
|
|
|
|
|
"yyyyMMddHHmm",
|
|
|
|
|
|
"yyyyMMddHHmmss",
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|