using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using OfficeOpenXml; using VolPro.Core.Extensions; using VolPro.Core.UserManager; using VolPro.Core.Utilities; namespace VolPro.Core.Generic { internal static class GenericExcelImportHelper { /// /// 从 Excel 流读取数据,按 CellOptions 校验表头和字典, /// 返回 WebResponseContent,成功时 Data 为 List<Dictionary<string,object>> /// public static WebResponseContent ReadRowsByCellOptions(string tableName, Stream excelStream, int importStartRowIndex = 1, HashSet ignoreFields=null) { var response = WebResponseContent.Instance; var resultRows = new List>(); if (excelStream == null || !excelStream.CanRead) return response.Error("未能读取上传的文件".Translator()); var cellOptions = GetCellOptions(tableName); if (cellOptions == null || cellOptions.Count == 0) return response.Error($"未找到表【{tableName}】的导出配置".Translator()); using var package = new ExcelPackage(excelStream); var sheet = package.Workbook.Worksheets.FirstOrDefault(); if (sheet?.Dimension == null || sheet.Dimension.End.Row <= importStartRowIndex) return response.Error("导入文件中没有数据".Translator()); if (ignoreFields!=null) { cellOptions = cellOptions.Where(x => !ignoreFields.Contains(x.ColumnName)).ToList(); } if (!BindHeaderIndexes(sheet, cellOptions, importStartRowIndex, out string headerError)) return response.Error(headerError); var dicNoKeys = cellOptions .Where(x => !string.IsNullOrEmpty(x.DropNo) && x.KeyValues != null && x.KeyValues.Keys.Count > 0) .Select(x => (x.ColumnName, x.SearchType, x.EditType)) .Distinct() .ToList(); var multiSelectColumns = dicNoKeys .Where(x => x.SearchType == "checkbox" || x.SearchType == "selectList" || x.SearchType == "treeSelect" || x.EditType == "checkbox" || x.EditType == "selectList" || x.EditType == "treeSelect") .Select(x => x.ColumnName) .ToArray(); // 日期字段类型(用于 Excel 数字日期转换) var dateFields = TableColumnContext.Data .Where(x => x.TableName == tableName && (x.ColumnType == "Date" || x.ColumnType == "DateTime")) .Select(s => s.ColumnName) .ToHashSet(StringComparer.OrdinalIgnoreCase); int rowStart = importStartRowIndex + 1; int rowEnd = sheet.Dimension.End.Row; int colStart = sheet.Dimension.Start.Column; int colEnd = sheet.Dimension.End.Column; for (int r = rowStart; r <= rowEnd; r++) { var rowDict = new Dictionary(StringComparer.OrdinalIgnoreCase); bool hasAnyValue = false; for (int c = colStart; c <= colEnd; c++) { var opt = cellOptions.FirstOrDefault(x => x.Index == c); if (opt == null) continue; string raw = sheet.Cells[r, c].Value?.ToString(); raw = raw?.Trim(); if (string.IsNullOrEmpty(raw)) { if (opt.Requierd) { string msg = "第[{$ts}]行,[{$ts}]验证未通过,不能为空" .TranslatorFormat(r, opt.ColumnCNName); return response.Error(msg); } rowDict[opt.ColumnName] = null; continue; } hasAnyValue = true; // 日期校验与转换(参考 EPPlusHelper.ReadToDataTable 中的日期处理): // 如果当前字段在 dateFields 中,且值为长度 5 的数字(Excel 序列号),则转换成 DateTime。 if (dateFields.Contains(opt.ColumnName) && raw.Length == 5 && int.TryParse(raw, out int days)) { var dt = new DateTime(1900, 1, 1).AddDays(days - 2); rowDict[opt.ColumnName] = dt; continue; } if (string.IsNullOrEmpty(opt.DropNo)) { rowDict[opt.ColumnName] = raw; continue; } if (opt.KeyValues == null) { return response.Error("[{$ts}]数据字典缺失".TranslatorFormat(opt.ColumnCNName)); } string key = null; if (multiSelectColumns.Contains(opt.ColumnName)) { var cellValues = raw.Replace(",", ",") .Split(",", StringSplitOptions.RemoveEmptyEntries); var keys = opt.KeyValues .Where(x => cellValues.Contains(x.Value)) .Select(s => s.Key) .ToArray(); if (cellValues.Length == keys.Length) { key = string.Join(",", keys); } } else { key = opt.KeyValues .Where(x => x.Value == raw) .Select(s => s.Key) .FirstOrDefault(); } if (key == null) { string values = string.Join(",", opt.KeyValues .Take(300) .Select(s => s.Value.Translator())); string msg = "第[{$ts}]行,[{$ts}]验证未通过,只能填写[{$ts}]" .TranslatorFormat(r, opt.ColumnCNName, values); return response.Error(msg); } rowDict[opt.ColumnName] = key; } if (!hasAnyValue) continue; resultRows.Add(rowDict); } return response.OK(null, resultRows); } private static List GetCellOptions(string tableName) { var mi = typeof(EPPlusHelper).GetMethod( "GetExportColumnInfo", BindingFlags.NonPublic | BindingFlags.Static); if (mi == null) return null; return mi.Invoke(null, new object[] { tableName, false, false, null }) as List; } private static bool BindHeaderIndexes( ExcelWorksheet sheet, List cellOptions, int headerRow, out string error) { error = null; for (int j = sheet.Dimension.Start.Column, k = sheet.Dimension.End.Column; j <= k; j++) { string columnCNName = sheet.Cells[headerRow, j].Value?.ToString()?.Trim(); if (string.IsNullOrEmpty(columnCNName)) continue; var options = cellOptions .FirstOrDefault(x => x.ColumnCNName.Translator() == columnCNName); if (options == null) { error = "[{$ts}]不是模板中的列".TranslatorReplace(columnCNName, true); return false; } if (options.Index > 0) { error = "[{$ts}]列名重复".TranslatorReplace(columnCNName, true); return false; } options.Index = j; } if (cellOptions.Exists(x => x.Index == 0)) { var errorOps = cellOptions .Where(x => x.Index == 0) .Select(s => s.ColumnCNName.Translator() + "," + s.ColumnName); error = $"{"导入文件列未找到字段".Translator()}:{string.Join("; ", errorOps)}"; return false; } return true; } } }