Files
FATrace/FATrace.WPLApp/Services/CsvServices.cs
2025-11-12 17:40:48 +08:00

136 lines
5.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Globalization;
using System.IO;
using CsvHelper;
using CsvHelper.Configuration;
using FATrace.WPLApp.ModelDto;
using Prism.Mvvm;
namespace FATrace.WPLApp.Services
{
/// <summary>
/// CSV服务
///导出CSV文件
/// </summary>
public class CsvServices:BindableBase
{
public ILogService LogService { get; }
public IFreeSql FreeSql { get; }
public CsvServices(ILogService logService, IFreeSql freeSql)
{
LogService = logService;
FreeSql = freeSql;
}
/// <summary>
/// 导出单条记录为 CSV 文件,文件名即记录的 InBagCode自动追加 .csv 扩展名)。
/// </summary>
/// <param name="item">要导出的数据项,类型为 RawProUserCsvDto</param>
/// <param name="outputDirectory">导出目录,不存在将自动创建</param>
/// <param name="overwrite">若目标文件已存在,是否允许覆盖</param>
/// <returns>导出后的完整文件路径</returns>
/// <exception cref="ArgumentNullException">item 或 outputDirectory 为 null</exception>
/// <exception cref="ArgumentException">InBagCode 为空或仅空白</exception>
/// <exception cref="IOException">文件已存在且不允许覆盖</exception>
public string ExportSingle(RawProUserCsvDto item, string outputDirectory, bool overwrite = false)
{
if (item is null) throw new ArgumentNullException(nameof(item));
if (outputDirectory is null) throw new ArgumentNullException(nameof(outputDirectory));
var inBagCode = (item.InBagCode ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(inBagCode))
throw new ArgumentException("InBagCode 不能为空", nameof(item.InBagCode));
// 目录确保存在
Directory.CreateDirectory(outputDirectory);
var safeName = SanitizeFileName(inBagCode);
var filePath = Path.Combine(outputDirectory, safeName + ".csv");
if (File.Exists(filePath) && !overwrite)
{
throw new IOException($"文件已存在且不允许覆盖: {filePath}");
}
// 使用 UTF8 BOM便于 Excel 正确识别中文
using var writer = new StreamWriter(filePath, false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
// 固定列顺序映射,并设置日期格式
csv.Context.RegisterClassMap<RawProUserCsvDtoMap>();
csv.WriteHeader<RawProUserCsvDto>();
csv.NextRecord();
csv.WriteRecord(item);
csv.NextRecord();
return filePath;
}
/// <summary>
/// 批量导出:把每一条记录分别导出为以各自 InBagCode 命名的 CSV 文件。
/// </summary>
/// <param name="items">数据集合</param>
/// <param name="outputDirectory">导出目录,不存在将自动创建</param>
/// <param name="overwrite">若目标文件已存在,是否允许覆盖</param>
/// <returns>成功导出的文件完整路径集合</returns>
public IEnumerable<string> ExportMany(IEnumerable<RawProUserCsvDto> items, string outputDirectory, bool overwrite = false)
{
if (items is null) throw new ArgumentNullException(nameof(items));
var results = new List<string>();
foreach (var item in items)
{
// 逐条导出,沿用相同规则
var path = ExportSingle(item, outputDirectory, overwrite);
results.Add(path);
}
return results;
}
/// <summary>
/// 清理文件名中的非法字符。
/// </summary>
private static string SanitizeFileName(string name)
{
var invalid = Path.GetInvalidFileNameChars();
var sb = new StringBuilder(name.Length);
foreach (var ch in name)
{
sb.Append(invalid.Contains(ch) ? '_' : ch);
}
return sb.ToString();
}
/// <summary>
/// RawProUserCsvDto 的 CSV 列映射,固定输出顺序并设置日期格式。
/// </summary>
private sealed class RawProUserCsvDtoMap : ClassMap<RawProUserCsvDto>
{
public RawProUserCsvDtoMap()
{
Map(x => x.RawCode).Index(0).Name(nameof(RawProUserCsvDto.RawCode));
Map(x => x.RawName).Index(1).Name(nameof(RawProUserCsvDto.RawName));
Map(x => x.InBagCode).Index(2).Name(nameof(RawProUserCsvDto.InBagCode));
Map(x => x.BoxCode).Index(3).Name(nameof(RawProUserCsvDto.BoxCode));
Map(x => x.Batch).Index(4).Name(nameof(RawProUserCsvDto.Batch));
Map(x => x.ShelfLife).Index(5).Name(nameof(RawProUserCsvDto.ShelfLife));
Map(x => x.Weight).Index(6).Name(nameof(RawProUserCsvDto.Weight));
Map(x => x.DeliveryDate).Index(7).Name(nameof(RawProUserCsvDto.DeliveryDate));
Map(x => x.RemainWeight).Index(8).Name(nameof(RawProUserCsvDto.RemainWeight));
Map(x => x.StockWeight).Index(9).Name(nameof(RawProUserCsvDto.StockWeight));
Map(x => x.WeightTime).Index(10).Name(nameof(RawProUserCsvDto.WeightTime)).TypeConverterOption.Format("yyyy-MM-dd HH:mm:ss");
Map(x => x.OpUser).Index(11).Name(nameof(RawProUserCsvDto.OpUser));
Map(x => x.CheckUser).Index(12).Name(nameof(RawProUserCsvDto.CheckUser));
Map(x => x.OutTime).Index(13).Name(nameof(RawProUserCsvDto.OutTime)).TypeConverterOption.Format("yyyy-MM-dd HH:mm:ss");
}
}
}
}