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 { /// /// CSV服务 ///导出CSV文件 /// public class CsvServices:BindableBase { public ILogService LogService { get; } public IFreeSql FreeSql { get; } public CsvServices(ILogService logService, IFreeSql freeSql) { LogService = logService; FreeSql = freeSql; } /// /// 导出单条记录为 CSV 文件,文件名即记录的 InBagCode(自动追加 .csv 扩展名)。 /// /// 要导出的数据项,类型为 RawProUserCsvDto /// 导出目录,不存在将自动创建 /// 若目标文件已存在,是否允许覆盖 /// 导出后的完整文件路径 /// item 或 outputDirectory 为 null /// InBagCode 为空或仅空白 /// 文件已存在且不允许覆盖 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(); csv.WriteHeader(); csv.NextRecord(); csv.WriteRecord(item); csv.NextRecord(); return filePath; } /// /// 批量导出:把每一条记录分别导出为以各自 InBagCode 命名的 CSV 文件。 /// /// 数据集合 /// 导出目录,不存在将自动创建 /// 若目标文件已存在,是否允许覆盖 /// 成功导出的文件完整路径集合 public IEnumerable ExportMany(IEnumerable items, string outputDirectory, bool overwrite = false) { if (items is null) throw new ArgumentNullException(nameof(items)); var results = new List(); foreach (var item in items) { // 逐条导出,沿用相同规则 var path = ExportSingle(item, outputDirectory, overwrite); results.Add(path); } return results; } /// /// 清理文件名中的非法字符。 /// 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(); } /// /// RawProUserCsvDto 的 CSV 列映射,固定输出顺序并设置日期格式。 /// private sealed class RawProUserCsvDtoMap : ClassMap { 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"); } } } }