using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using NCC.Common.Filter; using NCC.Common.Helper; using NCC.Dependency; using NCC.DynamicApiController; using NCC.Extend.Entitys.Dto.LqAnnualSummary; using NCC.Extend.Entitys.lq_annual_summary; using NCC.Extend.Entitys.lq_mdxx; using NCC.System.Entitys.Permission; using Mapster; using NPOI.HSSF.UserModel; using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; using SqlSugar; using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using Yitter.IdGenerator; namespace NCC.Extend { /// /// 年度汇总表服务 /// [ApiDescriptionSettings(Tag = "年度经营数据汇总", Name = "LqAnnualSummary", Order = 500)] [Route("api/Extend/[controller]")] public class LqAnnualSummaryService : IDynamicApiController, ITransient { private readonly ISqlSugarClient _db; /// /// 初始化一个类型的新实例 /// /// 数据库客户端 public LqAnnualSummaryService(ISqlSugarClient db) { _db = db; } #region 基础 CRUD /// /// 分页查询 /// [HttpGet("list")] public async Task GetList([FromQuery] AnnualSummaryQueryInput input) { var query = _db.Queryable() .Where(x => x.IsEffective == 1); if (!string.IsNullOrEmpty(input.StoreName)) { query = query.Where(x => x.StoreName.Contains(input.StoreName)); } if (!string.IsNullOrEmpty(input.StoreId)) { query = query.Where(x => x.StoreId == input.StoreId); } if (input.Year.HasValue) { query = query.Where(x => x.Year == input.Year.Value); } if (input.Month.HasValue) { query = query.Where(x => x.Month == input.Month.Value); } var list = await query.OrderBy(x => x.Year, OrderByType.Desc) .OrderBy(x => x.Month, OrderByType.Desc) .OrderBy(x => x.StoreId) .ToPagedListAsync(input.currentPage, input.pageSize); // 获取事业部名称映射 var buNameMap = await GetBusinessUnitNameMapAsync(); // 获取所有门店信息,用于获取正确的事业部信息 var storeIds = list.list.Select(x => x.StoreId).Distinct().ToList(); var stores = await _db.Queryable() .Where(x => storeIds.Contains(x.Id)) .Select(x => new { x.Id, x.Syb }) .ToListAsync(); var storeDict = stores.ToDictionary(x => x.Id, x => x); var result = new { pagination = list.pagination, list = list.list.Select(x => { // 确定正确的事业部ID string correctBuId = null; string correctBuName = null; // 1. 如果BusinessUnitId不为空,检查是否是科技部 if (!string.IsNullOrEmpty(x.BusinessUnitId)) { var buName = GetBusinessUnitDisplayName(x.BusinessUnitId, buNameMap); // 如果名称包含"科技",说明是科技部,需要从门店表获取事业部 if (buName.Contains("科技")) { // 从门店表获取事业部ID if (storeDict.ContainsKey(x.StoreId)) { correctBuId = storeDict[x.StoreId].Syb; if (!string.IsNullOrEmpty(correctBuId)) { correctBuName = GetBusinessUnitDisplayName(correctBuId, buNameMap); } } } else { // 不是科技部,使用原有的BusinessUnitId correctBuId = x.BusinessUnitId; correctBuName = buName; } } else if (!string.IsNullOrEmpty(x.BusinessUnitName)) { // 2. 如果BusinessUnitId为空,但BusinessUnitName不为空 var buName = GetBusinessUnitDisplayName(x.BusinessUnitName, buNameMap); if (buName.Contains("科技")) { // 从门店表获取事业部ID if (storeDict.ContainsKey(x.StoreId)) { correctBuId = storeDict[x.StoreId].Syb; if (!string.IsNullOrEmpty(correctBuId)) { correctBuName = GetBusinessUnitDisplayName(correctBuId, buNameMap); } } } else { // 尝试从BusinessUnitName中解析ID if (buNameMap.ContainsKey(x.BusinessUnitName)) { correctBuName = buNameMap[x.BusinessUnitName]; } else { correctBuName = buName; } } } else { // 3. 如果都为空,从门店表获取事业部 if (storeDict.ContainsKey(x.StoreId)) { correctBuId = storeDict[x.StoreId].Syb; if (!string.IsNullOrEmpty(correctBuId)) { correctBuName = GetBusinessUnitDisplayName(correctBuId, buNameMap); } } } // 如果还是没有找到,返回"未知" if (string.IsNullOrEmpty(correctBuName)) { correctBuName = "未知"; } return new { id = x.Id, storeId = x.StoreId, storeName = x.StoreName, businessUnitId = correctBuId, businessUnitName = correctBuName, year = x.Year, month = x.Month, totalPerformance = x.TotalPerformance, totalConsume = x.TotalConsume, headCount = x.HeadCount, personTime = x.PersonTime, projectCount = x.ProjectCount }; }).ToList() }; return result; } /// /// 保存(新增或更新) /// [HttpPost("save")] public async Task Save([FromBody] AnnualSummaryInput input) { if (string.IsNullOrEmpty(input.Id)) { // Uniqueness check var exist = await _db.Queryable() .AnyAsync(x => x.StoreId == input.StoreId && x.Year == input.Year && x.Month == input.Month && x.IsEffective == 1); if (exist) throw new Exception($"该门店 {input.Year}年{input.Month}月 数据已存在"); var entity = input.Adapt(); entity.Id = YitIdHelper.NextId().ToString(); entity.CreateTime = DateTime.Now; entity.IsEffective = 1; await _db.Insertable(entity).ExecuteCommandAsync(); } else { var entity = await _db.Queryable().FirstAsync(x => x.Id == input.Id); if (entity == null) throw new Exception("数据不存在"); // Update fields entity.TotalPerformance = input.TotalPerformance; entity.TotalConsume = input.TotalConsume; entity.HeadCount = input.HeadCount; entity.PersonTime = input.PersonTime; entity.ProjectCount = input.ProjectCount; entity.UpdateTime = DateTime.Now; await _db.Updateable(entity).ExecuteCommandAsync(); } } /// /// 删除 /// [HttpPost("delete")] public async Task Delete([FromBody] List ids) { await _db.Updateable() .SetColumns(x => x.IsEffective == 0) .Where(x => ids.Contains(x.Id)) .ExecuteCommandAsync(); } #endregion #region Excel 导入导出 /// /// 导入数据 /// [HttpPost("import")] public async Task Import(IFormFile file) { if (file == null || file.Length == 0) throw new Exception("请上传文件"); // 检查文件格式 var allowedExtensions = new[] { ".xlsx", ".xls" }; var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant(); if (!allowedExtensions.Contains(fileExtension)) { throw new Exception("只支持.xlsx和.xls格式的Excel文件"); } // 保存临时文件 var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName)); try { using (var stream = new FileStream(tempFilePath, FileMode.Create)) { await file.CopyToAsync(stream); } // 使用NPOI直接读取Excel文件,支持读取公式的计算值 var list = new List(); var errorMessages = new List(); using (var fileStream = new FileStream(tempFilePath, FileMode.Open, FileAccess.Read)) { IWorkbook workbook = null; if (fileExtension == ".xlsx") { workbook = new XSSFWorkbook(fileStream); } else { workbook = new HSSFWorkbook(fileStream); } ISheet sheet = workbook.GetSheetAt(0); // 第一个工作表 if (sheet == null || sheet.LastRowNum < 1) { throw new Exception("Excel文件中没有数据行(至少需要标题行和一行数据)"); } // 读取标题行,确定列索引 var headerRow = sheet.GetRow(0); if (headerRow == null) { throw new Exception("Excel文件第一行(标题行)为空"); } // 根据列名查找列索引(支持多种可能的列名) int GetColumnIndex(string[] possibleNames) { for (int col = 0; col < headerRow.LastCellNum; col++) { var cell = headerRow.GetCell(col); if (cell == null) continue; var columnName = GetCellValue(cell)?.Trim() ?? ""; foreach (var name in possibleNames) { if (columnName == name || columnName.Contains(name) || name.Contains(columnName)) { return col; } } } return -1; } // 查找各列的索引(根据实际Excel格式) var businessUnitColIndex = GetColumnIndex(new[] { "事业部", "归属事业部", "BusinessUnit" }); var storeNameColIndex = GetColumnIndex(new[] { "门店", "门店名称", "店名", "店铺名称" }); var yearColIndex = GetColumnIndex(new[] { "年份", "年", "Year" }); var monthColIndex = GetColumnIndex(new[] { "月份", "月", "Month" }); var perfColIndex = GetColumnIndex(new[] { "总业绩", "业绩", "总营业额", "TotalPerformance" }); var consumeColIndex = GetColumnIndex(new[] { "总消耗", "消耗", "TotalConsume" }); var headColIndex = GetColumnIndex(new[] { "人头数", "客头数", "HeadCount" }); var personColIndex = GetColumnIndex(new[] { "人次数", "客次数", "PersonTime" }); var projectColIndex = GetColumnIndex(new[] { "项目数", "总项目数", "ProjectCount" }); // 验证必填列 if (storeNameColIndex == -1) { throw new Exception("Excel文件中未找到门店名称列,请确保第一行包含'门店名称'列"); } if (yearColIndex == -1) { throw new Exception("Excel文件中未找到年份列,请确保第一行包含'年份'列"); } if (monthColIndex == -1) { throw new Exception("Excel文件中未找到月份列,请确保第一行包含'月份'列"); } // 从第1行开始读取数据(跳过标题行,索引0是标题行) for (int i = 1; i <= sheet.LastRowNum; i++) { var row = sheet.GetRow(i); if (row == null) continue; try { // 读取门店名称,支持公式计算值 var storeNameCell = row.GetCell(storeNameColIndex); if (storeNameCell == null) continue; string storeName = GetCellValue(storeNameCell); if (string.IsNullOrWhiteSpace(storeName)) continue; // 跳过空行 // 检测是否为公式文本(如果GetCellValue返回的是公式文本,说明计算失败) if (storeName.StartsWith("_xlfn.") || storeName.StartsWith("=") || storeName.Contains("XLOOKUP") || storeName.Contains("VLOOKUP")) { errorMessages.Add($"第{i + 1}行:门店名称包含无法计算的公式({storeName}),已跳过。请确保门店名称列是实际值而不是公式。"); continue; } // 读取事业部名称(从Excel中读取) string businessUnitName = null; if (businessUnitColIndex >= 0) { var businessUnitCell = row.GetCell(businessUnitColIndex); if (businessUnitCell != null) { businessUnitName = GetCellValue(businessUnitCell); if (!string.IsNullOrWhiteSpace(businessUnitName)) { businessUnitName = businessUnitName.Trim(); } } } // 读取其他字段 var yearCell = row.GetCell(yearColIndex); var monthCell = row.GetCell(monthColIndex); var perfCell = perfColIndex >= 0 ? row.GetCell(perfColIndex) : null; var consumeCell = consumeColIndex >= 0 ? row.GetCell(consumeColIndex) : null; var headCell = headColIndex >= 0 ? row.GetCell(headColIndex) : null; var personCell = personColIndex >= 0 ? row.GetCell(personColIndex) : null; var projectCell = projectColIndex >= 0 ? row.GetCell(projectColIndex) : null; // 解析年份(支持"2024年"格式) int year = 0; var yearStr = GetCellValue(yearCell); if (!string.IsNullOrWhiteSpace(yearStr)) { // 提取数字部分(如"2024年" -> "2024") var yearMatch = Regex.Match(yearStr, @"(\d{4})"); if (yearMatch.Success) { int.TryParse(yearMatch.Groups[1].Value, out year); } else { int.TryParse(yearStr, out year); } } // 解析月份(支持"1月"、"01月"格式) int month = 0; var monthStr = GetCellValue(monthCell); if (!string.IsNullOrWhiteSpace(monthStr)) { // 提取数字部分(如"1月" -> "1") var monthMatch = Regex.Match(monthStr, @"(\d{1,2})"); if (monthMatch.Success) { int.TryParse(monthMatch.Groups[1].Value, out month); } else { int.TryParse(monthStr, out month); } } var item = new AnnualSummaryImportDto { StoreName = storeName.Trim(), BusinessUnitName = businessUnitName, Year = year, Month = month, TotalPerformance = perfCell != null && decimal.TryParse(GetCellValue(perfCell), out var perf) ? perf : 0m, TotalConsume = consumeCell != null && decimal.TryParse(GetCellValue(consumeCell), out var consume) ? consume : 0m, HeadCount = headCell != null && decimal.TryParse(GetCellValue(headCell), out var head) ? head : 0m, PersonTime = personCell != null && decimal.TryParse(GetCellValue(personCell), out var person) ? person : 0m, ProjectCount = projectCell != null && decimal.TryParse(GetCellValue(projectCell), out var project) ? project : 0m }; list.Add(item); } catch (Exception ex) { errorMessages.Add($"第{i + 1}行:读取数据时出错 - {ex.Message}"); continue; } } workbook.Close(); } if (!list.Any()) { if (errorMessages.Any()) { throw new Exception($"导入失败:没有有效数据。\n{string.Join("\n", errorMessages)}"); } throw new Exception("导入失败:Excel文件中没有有效数据行"); } // 预加载所有门店信息,用于匹配 StoreId 和 BusinessUnit var allStores = await _db.Queryable().ToListAsync(); // 预加载所有组织机构信息,用于匹配事业部 var allOrgs = await _db.Queryable() .Where(x => x.DeleteMark == null) .Select(x => new { x.Id, x.FullName }) .ToListAsync(); var entities = new List(); var successCount = 0; var failCount = 0; foreach (var item in list) { if (string.IsNullOrEmpty(item.StoreName)) { failCount++; continue; } try { // 增强匹配逻辑:支持模糊匹配和简称 // Excel中的门店名称可能是简化名称(如"紫荆"),需要匹配完整名称(如"绿纤紫荆店") var store = allStores.FirstOrDefault(x => x.Dm == item.StoreName); if (store == null) { // 精确匹配(包含关系) store = allStores.FirstOrDefault(x => x.Dm.Contains(item.StoreName)); } if (store == null) { // 去除 绿纤 和 店 之后再比 var cleanExcelName = item.StoreName.Replace("绿纤", "").Replace("店", "").Trim(); if (!string.IsNullOrEmpty(cleanExcelName)) { store = allStores.FirstOrDefault(x => x.Dm.Replace("绿纤", "").Replace("店", "").Trim() == cleanExcelName); } } if (store == null) { // 反向匹配:Excel名称可能是门店名称的一部分(如"紫荆"匹配"绿纤紫荆店") store = allStores.FirstOrDefault(x => x.Dm.Replace("绿纤", "").Replace("店", "").Trim().Contains(item.StoreName.Trim())); } if (store == null) { // 最后尝试:Excel名称包含门店名称的一部分(如"静居寺"匹配"绿纤静居寺店") store = allStores.FirstOrDefault(x => item.StoreName.Trim().Contains(x.Dm.Replace("绿纤", "").Replace("店", "").Trim()) || x.Dm.Replace("绿纤", "").Replace("店", "").Trim().Contains(item.StoreName.Trim())); } if (store == null) { failCount++; errorMessages.Add($"门店【{item.StoreName}】未找到匹配的门店。请确保 Excel 中的门店名称在系统中存在(系统示例:{allStores.FirstOrDefault()?.Dm ?? "无"})"); continue; } var entity = new LqAnnualSummaryEntity { StoreId = store.Id, StoreName = store.Dm, Year = item.Year, Month = item.Month, TotalPerformance = item.TotalPerformance, TotalConsume = item.TotalConsume, HeadCount = item.HeadCount, PersonTime = item.PersonTime, ProjectCount = item.ProjectCount, IsEffective = 1, CreateTime = DateTime.Now, CreateUser = "Import" }; // 处理事业部信息 // 1. 优先从Excel中读取的事业部名称匹配组织表(只匹配事业部,不匹配科技部) if (!string.IsNullOrEmpty(item.BusinessUnitName)) { // 先尝试精确匹配 var org = allOrgs.FirstOrDefault(x => x.FullName == item.BusinessUnitName); // 如果精确匹配失败,尝试模糊匹配,但只匹配包含"事业"的组织(排除科技部) if (org == null) { org = allOrgs.FirstOrDefault(x => x.FullName.Contains("事业") && // 确保是事业部 (x.FullName.Contains(item.BusinessUnitName) || item.BusinessUnitName.Contains(x.FullName))); } if (org != null) { // 验证匹配到的组织确实是事业部(不是科技部) if (org.FullName.Contains("事业") && !org.FullName.Contains("科技")) { entity.BusinessUnitId = org.Id; entity.BusinessUnitName = org.FullName; } else { // 如果匹配到的是科技部,记录错误,从门店表获取事业部 errorMessages.Add($"门店【{item.StoreName}】的事业部【{item.BusinessUnitName}】匹配到了科技部,已从门店表获取正确的事业部信息。"); // 从门店表获取事业部 if (!string.IsNullOrEmpty(store.Syb)) { // 设置事业部ID entity.BusinessUnitId = store.Syb; var sybOrg = allOrgs.FirstOrDefault(x => x.Id == store.Syb); if (sybOrg != null) { // 设置事业部名称 entity.BusinessUnitName = sybOrg.FullName; } else { // 如果找不到组织,记录警告 errorMessages.Add($"门店【{item.StoreName}】的事业部ID【{store.Syb}】未找到对应的组织名称。"); entity.BusinessUnitName = null; } } else { // 如果门店表中也没有事业部信息 entity.BusinessUnitId = null; entity.BusinessUnitName = null; } } } else { // 如果找不到匹配的组织,从门店表获取事业部 errorMessages.Add($"门店【{item.StoreName}】的事业部【{item.BusinessUnitName}】未找到匹配的组织,已从门店表获取事业部信息。"); if (!string.IsNullOrEmpty(store.Syb)) { // 设置事业部ID entity.BusinessUnitId = store.Syb; var sybOrg = allOrgs.FirstOrDefault(x => x.Id == store.Syb); if (sybOrg != null) { // 设置事业部名称 entity.BusinessUnitName = sybOrg.FullName; } else { // 如果找不到组织,记录警告 errorMessages.Add($"门店【{item.StoreName}】的事业部ID【{store.Syb}】未找到对应的组织名称。"); entity.BusinessUnitName = null; } } else { // 如果门店表中也没有事业部信息 entity.BusinessUnitId = null; entity.BusinessUnitName = null; } } } else { // 2. 如果Excel中没有事业部信息,从门店信息中获取 Syb (事业部) // 注意:只使用Syb,不使用Kjb(科技部) string buId = store.Syb; if (!string.IsNullOrEmpty(buId)) { // 设置事业部ID entity.BusinessUnitId = buId; // 从组织表获取事业部名称 var org = allOrgs.FirstOrDefault(x => x.Id == buId); if (org != null) { // 设置事业部名称 entity.BusinessUnitName = org.FullName; } else { // 如果找不到组织,记录警告,但不设置名称(保持为空) errorMessages.Add($"门店【{item.StoreName}】的事业部ID【{buId}】未找到对应的组织名称。"); entity.BusinessUnitName = null; // 明确设置为null,而不是ID } } else { // 如果门店表中也没有事业部信息,记录警告 errorMessages.Add($"门店【{item.StoreName}】没有事业部信息,请检查门店配置。"); // 明确设置为null entity.BusinessUnitId = null; entity.BusinessUnitName = null; } } entities.Add(entity); } catch (Exception ex) { failCount++; errorMessages.Add($"处理门店【{item.StoreName}】时出错:{ex.Message}"); continue; } } // 批量处理:覆盖逻辑 // 先删除已存在的 (StoreId + Year + Month) foreach (var group in entities.GroupBy(x => new { x.StoreId, x.Year, x.Month })) { // 删除旧数据 await _db.Deleteable() .Where(x => x.StoreId == group.Key.StoreId && x.Year == group.Key.Year && x.Month == group.Key.Month) .ExecuteCommandAsync(); // 插入新数据 (取Excel中最后一条,防止Excel自身重复) var toInsert = group.Last(); toInsert.Id = YitIdHelper.NextId().ToString(); toInsert.CreateTime = DateTime.Now; toInsert.CreateUser = "Import"; await _db.Insertable(toInsert).ExecuteCommandAsync(); successCount++; } // 返回导入结果 var resultMessage = $"导入完成:成功 {successCount} 条"; if (failCount > 0 || errorMessages.Any()) { resultMessage += $",失败 {failCount} 条"; if (errorMessages.Any()) { resultMessage += $"\n错误详情:\n{string.Join("\n", errorMessages)}"; } } // 如果有错误但部分成功,返回警告信息(通过异常返回,但包含成功信息) if (successCount > 0 && (failCount > 0 || errorMessages.Any())) { // 部分成功,抛出包含成功和失败信息的异常 throw new Exception(resultMessage); } else if (successCount == 0) { // 全部失败,抛出异常 throw new Exception(resultMessage); } } finally { // 删除临时文件 if (File.Exists(tempFilePath)) { File.Delete(tempFilePath); } } } #endregion #region 统计报表 /// /// 4.1 全年门店业绩表 /// [HttpPost("GetTotalPerformanceStat")] public async Task GetTotalPerformanceStat([FromBody] AnnualSummaryQueryInput input) { return await GetMonthlyStat(input, x => x.TotalPerformance); } /// /// 4.2 全年门店消耗表 /// [HttpPost("GetTotalConsumeStat")] public async Task GetTotalConsumeStat([FromBody] AnnualSummaryQueryInput input) { return await GetMonthlyStat(input, x => x.TotalConsume); } /// /// 4.3 年度门店人头表 /// [HttpPost("GetHeadCountStat")] public async Task GetHeadCountStat([FromBody] AnnualSummaryQueryInput input) { return await GetMonthlyStat(input, x => x.HeadCount); } /// /// 4.4 年度门店项目数表 /// [HttpPost("GetProjectCountStat")] public async Task GetProjectCountStat([FromBody] AnnualSummaryQueryInput input) { return await GetMonthlyStat(input, x => x.ProjectCount); } /// /// 4.5 年度门店人次表 /// [HttpGet("GetPersonTimeStat")] public async Task GetPersonTimeStat([FromQuery] AnnualSummaryQueryInput input) { return await GetMonthlyStat(input, x => x.PersonTime); } /// /// 通用月度趋势统计 /// [HttpGet("GetMonthlyTrend")] public async Task GetMonthlyTrend([FromQuery] AnnualSummaryQueryInput input, [FromQuery] string type) { // 确保 type 参数正确绑定 if (string.IsNullOrEmpty(type)) { type = "totalperformance"; // 默认值 } // 根据 type 参数选择正确的字段选择器 Func fieldSelector; switch (type.ToLower()) { case "totalperformance": fieldSelector = x => x.TotalPerformance; break; case "totalconsume": fieldSelector = x => x.TotalConsume; break; case "headcount": fieldSelector = x => x.HeadCount; break; case "persontime": fieldSelector = x => x.PersonTime; break; case "projectcount": fieldSelector = x => x.ProjectCount; break; default: fieldSelector = x => x.TotalPerformance; break; } return await GetMonthlyStat(input, fieldSelector); } /// /// 4.6 门店五项指标统计图 /// [HttpPost("GetStoreIndicatorsStat")] public async Task GetStoreIndicatorsStat([FromBody] AnnualSummaryQueryInput input) { return await GetIndicatorStat(input, x => x.TotalPerformance); } /// /// 获取门店指标详情 /// /// 查询参数 /// 指标类型 /// 指标统计输出 [HttpGet("GetStoreIndicatorDetail")] public async Task GetStoreIndicatorDetail([FromQuery] AnnualSummaryQueryInput input, string type) { switch (type?.ToLower()) { case "totalperformance": return await GetIndicatorStat(input, x => x.TotalPerformance); case "totalconsume": return await GetIndicatorStat(input, x => x.TotalConsume); case "headcount": return await GetIndicatorStat(input, x => x.HeadCount); case "persontime": return await GetIndicatorStat(input, x => x.PersonTime); case "projectcount": return await GetIndicatorStat(input, x => x.ProjectCount); default: return await GetIndicatorStat(input, x => x.TotalPerformance); } } /// /// 4.7 事业部五项指标总计图 /// [HttpGet("GetBusinessUnitIndicatorsStat")] public async Task GetBusinessUnitIndicatorsStat([FromQuery] AnnualSummaryQueryInput input, string type) { // 类似 4.6,但是按 BusinessUnitName 分组 switch (type?.ToLower()) { case "totalperformance": return await GetBuIndicatorStat(input, x => x.TotalPerformance); case "totalconsume": return await GetBuIndicatorStat(input, x => x.TotalConsume); case "headcount": return await GetBuIndicatorStat(input, x => x.HeadCount); case "persontime": return await GetBuIndicatorStat(input, x => x.PersonTime); case "projectcount": return await GetBuIndicatorStat(input, x => x.ProjectCount); default: return await GetBuIndicatorStat(input, x => x.TotalPerformance); } } /// /// 4.8 事业部内部汇总 (宽表) /// [HttpPost("GetBusinessUnitSummaryStat")] public async Task GetBusinessUnitSummaryStat([FromBody] AnnualSummaryQueryInput input) { int year = input.Year ?? DateTime.Now.Year; // 获取事业部名称映射 var buNameMap = await GetBusinessUnitNameMapAsync(); var currentData = await _db.Queryable() .Where(x => x.IsEffective == 1 && x.Year == year) .WhereIF(!string.IsNullOrEmpty(input.StoreName), x => x.StoreName.Contains(input.StoreName)) .ToListAsync(); var lastYearData = await _db.Queryable() .Where(x => x.IsEffective == 1 && x.Year == year - 1) .WhereIF(!string.IsNullOrEmpty(input.StoreName), x => x.StoreName.Contains(input.StoreName)) .ToListAsync(); // 聚合数据:按 StoreId 分组,解决同一门店对应多个事业部的问题 // 先获取所有涉及的门店,按 StoreId 分组,优先选择有 BusinessUnitId 且数据更多的记录 var allStoreGroups = currentData.Select(x => new { x.StoreId, x.StoreName, x.BusinessUnitName, x.BusinessUnitId }) .Union(lastYearData.Select(x => new { x.StoreId, x.StoreName, x.BusinessUnitName, x.BusinessUnitId })) .GroupBy(x => x.StoreId) .Select(g => { // 优先选择有 BusinessUnitId 的记录,如果都有或都没有,选择第一条 var preferred = g.OrderByDescending(x => !string.IsNullOrEmpty(x.BusinessUnitId)) .ThenByDescending(x => x.BusinessUnitName) .First(); return new { preferred.StoreId, preferred.StoreName, preferred.BusinessUnitName, preferred.BusinessUnitId }; }) .OrderBy(x => x.BusinessUnitName) .ThenBy(x => x.StoreName) .ToList(); var allStores = allStoreGroups; // 批量获取门店开业时间 var storeIds = allStores.Select(x => x.StoreId).Distinct().ToList(); var storeOpenTimes = await GetStoreOpenTimesAsync(storeIds); var output = new BusinessUnitSummaryOutput(); foreach (var store in allStores) { // Current Year Sums var curr = currentData.Where(x => x.StoreId == store.StoreId).ToList(); var last = lastYearData.Where(x => x.StoreId == store.StoreId).ToList(); // 优先使用BusinessUnitId,如果没有则使用BusinessUnitName var buId = store.BusinessUnitId ?? store.BusinessUnitName; var buDisplayName = GetBusinessUnitDisplayName(buId, buNameMap); // 获取门店开业时间 var openTime = storeOpenTimes.ContainsKey(store.StoreId) ? storeOpenTimes[store.StoreId] : null; var row = new BusinessUnitSummaryRow { BusinessUnitName = buDisplayName, StoreName = store.StoreName, CurrentYearPerformance = curr.Sum(x => x.TotalPerformance), LastYearPerformance = last.Sum(x => x.TotalPerformance), CurrentYearConsume = curr.Sum(x => x.TotalConsume), LastYearConsume = last.Sum(x => x.TotalConsume), CurrentYearHeadCount = curr.Sum(x => x.HeadCount), LastYearHeadCount = last.Sum(x => x.HeadCount), CurrentYearPersonTime = curr.Sum(x => x.PersonTime), LastYearPersonTime = last.Sum(x => x.PersonTime), CurrentYearProjectCount = curr.Sum(x => x.ProjectCount), LastYearProjectCount = last.Sum(x => x.ProjectCount), }; // 使用新的增长率计算方法(基于月均,考虑实际营业月份) // 对于单个门店,需要考虑开业时间 row.PerformanceGrowthRate = CalculateGrowthRateByAverage(curr, last, openTime, year, x => x.TotalPerformance); row.ConsumeGrowthRate = CalculateGrowthRateByAverage(curr, last, openTime, year, x => x.TotalConsume); row.HeadCountGrowthRate = CalculateGrowthRateByAverage(curr, last, openTime, year, x => x.HeadCount); row.PersonTimeGrowthRate = CalculateGrowthRateByAverage(curr, last, openTime, year, x => x.PersonTime); row.ProjectCountGrowthRate = CalculateGrowthRateByAverage(curr, last, openTime, year, x => x.ProjectCount); output.Rows.Add(row); } return new { list = output.Rows.Select(x => new { businessUnitName = x.BusinessUnitName, storeName = x.StoreName, currentPerformance = x.CurrentYearPerformance, lastPerformance = x.LastYearPerformance, performanceGrowthRate = x.PerformanceGrowthRate, currentConsume = x.CurrentYearConsume, lastConsume = x.LastYearConsume, consumeGrowthRate = x.ConsumeGrowthRate, currentHeadCount = x.CurrentYearHeadCount, lastHeadCount = x.LastYearHeadCount, headCountGrowthRate = x.HeadCountGrowthRate, currentPersonTime = x.CurrentYearPersonTime, lastPersonTime = x.LastYearPersonTime, personTimeGrowthRate = x.PersonTimeGrowthRate, currentProjectCount = x.CurrentYearProjectCount, lastProjectCount = x.LastYearProjectCount, projectCountGrowthRate = x.ProjectCountGrowthRate }).ToList() }; } #endregion #region 私有辅助方法 private async Task GetMonthlyStat(AnnualSummaryQueryInput input, Func fieldSelector) { int year = input.Year ?? DateTime.Now.Year; // 获取事业部名称映射 var buNameMap = await GetBusinessUnitNameMapAsync(); // 获取本年度数据 var currentYearData = await _db.Queryable() .Where(x => x.IsEffective == 1 && x.Year == year) .WhereIF(!string.IsNullOrEmpty(input.StoreName), x => x.StoreName.Contains(input.StoreName)) .ToListAsync(); // 获取上年度数据 (用于计算同比/增长率) var lastYearData = await _db.Queryable() .Where(x => x.IsEffective == 1 && x.Year == year - 1) .WhereIF(!string.IsNullOrEmpty(input.StoreName), x => x.StoreName.Contains(input.StoreName)) .ToListAsync(); // 整理所有门店 (包括今年有数据和去年有数据的) // 按门店ID去重,确保每个门店只出现一次 // 如果同一门店有多个事业部,取第一个(通常应该只有一个) var allStores = currentYearData .GroupBy(x => x.StoreId) .Select(g => new { StoreId = g.Key, StoreName = g.First().StoreName, BusinessUnitName = g.First().BusinessUnitName, BusinessUnitId = g.First().BusinessUnitId }) .OrderBy(x => x.BusinessUnitName) .ThenBy(x => x.StoreName) .ToList(); // 批量获取门店开业时间 var storeIds = allStores.Select(x => x.StoreId).Distinct().ToList(); var storeOpenTimes = await GetStoreOpenTimesAsync(storeIds); var output = new AnnualMonthlyStatOutput(); foreach (var store in allStores) { // 优先使用BusinessUnitId,如果没有则使用BusinessUnitName var buId = store.BusinessUnitId ?? store.BusinessUnitName; var buDisplayName = GetBusinessUnitDisplayName(buId, buNameMap); var row = new MonthlyDataRow { BusinessUnitName = buDisplayName, StoreName = store.StoreName }; var storeCurrData = currentYearData.Where(x => x.StoreId == store.StoreId).ToList(); var storeLastData = lastYearData.Where(x => x.StoreId == store.StoreId).ToList(); // 获取门店开业时间 var openTime = storeOpenTimes.ContainsKey(store.StoreId) ? storeOpenTimes[store.StoreId] : null; // Fill months (Current Year) - 优化:避免重复调用 FirstOrDefault for (int month = 1; month <= 12; month++) { var currMonthData = storeCurrData.FirstOrDefault(x => x.Month == month); var lastMonthData = storeLastData.FirstOrDefault(x => x.Month == month); switch (month) { case 1: row.Month1 = currMonthData != null ? fieldSelector(currMonthData) : 0; row.LastMonth1 = lastMonthData != null ? fieldSelector(lastMonthData) : 0; break; case 2: row.Month2 = currMonthData != null ? fieldSelector(currMonthData) : 0; row.LastMonth2 = lastMonthData != null ? fieldSelector(lastMonthData) : 0; break; case 3: row.Month3 = currMonthData != null ? fieldSelector(currMonthData) : 0; row.LastMonth3 = lastMonthData != null ? fieldSelector(lastMonthData) : 0; break; case 4: row.Month4 = currMonthData != null ? fieldSelector(currMonthData) : 0; row.LastMonth4 = lastMonthData != null ? fieldSelector(lastMonthData) : 0; break; case 5: row.Month5 = currMonthData != null ? fieldSelector(currMonthData) : 0; row.LastMonth5 = lastMonthData != null ? fieldSelector(lastMonthData) : 0; break; case 6: row.Month6 = currMonthData != null ? fieldSelector(currMonthData) : 0; row.LastMonth6 = lastMonthData != null ? fieldSelector(lastMonthData) : 0; break; case 7: row.Month7 = currMonthData != null ? fieldSelector(currMonthData) : 0; row.LastMonth7 = lastMonthData != null ? fieldSelector(lastMonthData) : 0; break; case 8: row.Month8 = currMonthData != null ? fieldSelector(currMonthData) : 0; row.LastMonth8 = lastMonthData != null ? fieldSelector(lastMonthData) : 0; break; case 9: row.Month9 = currMonthData != null ? fieldSelector(currMonthData) : 0; row.LastMonth9 = lastMonthData != null ? fieldSelector(lastMonthData) : 0; break; case 10: row.Month10 = currMonthData != null ? fieldSelector(currMonthData) : 0; row.LastMonth10 = lastMonthData != null ? fieldSelector(lastMonthData) : 0; break; case 11: row.Month11 = currMonthData != null ? fieldSelector(currMonthData) : 0; row.LastMonth11 = lastMonthData != null ? fieldSelector(lastMonthData) : 0; break; case 12: row.Month12 = currMonthData != null ? fieldSelector(currMonthData) : 0; row.LastMonth12 = lastMonthData != null ? fieldSelector(lastMonthData) : 0; break; } } row.TotalCurrentYear = storeCurrData.Sum(x => fieldSelector(x)); // 使用新的月均计算方法(考虑开业时间) row.AvgCurrentYear = CalculateMonthlyAverage(storeCurrData, openTime, year, fieldSelector); row.TotalLastYear = storeLastData.Sum(x => fieldSelector(x)); // 使用新的月均计算方法(考虑开业时间) row.AvgLastYear = CalculateMonthlyAverage(storeLastData, openTime, year - 1, fieldSelector); // 使用新的增长率计算方法(基于月均) row.GrowthRate = CalculateGrowthRateByAverage(storeCurrData, storeLastData, openTime, year, fieldSelector); output.Rows.Add(row); } return new { monthColumns = output.MonthColumns.Select(c => c.Substring(0, 1).ToLower() + c.Substring(1)).ToList(), rows = output.Rows.Select(x => new { businessUnitName = x.BusinessUnitName, storeName = x.StoreName, month1 = x.Month1, month2 = x.Month2, month3 = x.Month3, month4 = x.Month4, month5 = x.Month5, month6 = x.Month6, month7 = x.Month7, month8 = x.Month8, month9 = x.Month9, month10 = x.Month10, month11 = x.Month11, month12 = x.Month12, lastMonth1 = x.LastMonth1, lastMonth2 = x.LastMonth2, lastMonth3 = x.LastMonth3, lastMonth4 = x.LastMonth4, lastMonth5 = x.LastMonth5, lastMonth6 = x.LastMonth6, lastMonth7 = x.LastMonth7, lastMonth8 = x.LastMonth8, lastMonth9 = x.LastMonth9, lastMonth10 = x.LastMonth10, lastMonth11 = x.LastMonth11, lastMonth12 = x.LastMonth12, totalCurrentYear = x.TotalCurrentYear, avgCurrentYear = x.AvgCurrentYear, totalLastYear = x.TotalLastYear, avgLastYear = x.AvgLastYear, growthRate = x.GrowthRate }).ToList() }; } private async Task GetIndicatorStat(AnnualSummaryQueryInput input, Func fieldSelector) { int year = input.Year ?? DateTime.Now.Year; // 获取事业部名称映射 var buNameMap = await GetBusinessUnitNameMapAsync(); var currentData = await _db.Queryable() .Where(x => x.IsEffective == 1 && x.Year == year) .WhereIF(!string.IsNullOrEmpty(input.StoreName), x => x.StoreName.Contains(input.StoreName)) .ToListAsync(); var lastData = await _db.Queryable() .Where(x => x.IsEffective == 1 && x.Year == year - 1) .WhereIF(!string.IsNullOrEmpty(input.StoreName), x => x.StoreName.Contains(input.StoreName)) .ToListAsync(); // 按门店ID去重,确保每个门店只出现一次 // 如果同一门店有多个事业部,取第一个(通常应该只有一个) var allStores = currentData .GroupBy(x => x.StoreId) .Select(g => new { StoreId = g.Key, StoreName = g.First().StoreName, BusinessUnitName = g.First().BusinessUnitName, BusinessUnitId = g.First().BusinessUnitId }) .OrderBy(x => x.BusinessUnitName) .ThenBy(x => x.StoreName) .ToList(); // 批量获取门店开业时间 var storeIds = allStores.Select(x => x.StoreId).Distinct().ToList(); var storeOpenTimes = await GetStoreOpenTimesAsync(storeIds); var output = new IndicatorStatOutput(); foreach (var store in allStores) { // 按门店ID汇总所有月份的数据 var storeCurrData = currentData.Where(x => x.StoreId == store.StoreId).ToList(); var storeLastData = lastData.Where(x => x.StoreId == store.StoreId).ToList(); var curVal = storeCurrData.Sum(x => fieldSelector(x)); var lastVal = storeLastData.Sum(x => fieldSelector(x)); // 优先使用BusinessUnitId,如果没有则使用BusinessUnitName var buId = store.BusinessUnitId ?? store.BusinessUnitName; var buDisplayName = GetBusinessUnitDisplayName(buId, buNameMap); // 获取门店开业时间 var openTime = storeOpenTimes.ContainsKey(store.StoreId) ? storeOpenTimes[store.StoreId] : null; // 使用新的增长率计算方法(基于月均) var growthRate = CalculateGrowthRateByAverage(storeCurrData, storeLastData, openTime, year, fieldSelector); output.Rows.Add(new IndicatorDataRow { BusinessUnitName = buDisplayName, StoreName = store.StoreName, CurrentYearValue = curVal, LastYearValue = lastVal, GrowthRate = growthRate }); } return new { rows = output.Rows.Select(x => new { businessUnitName = x.BusinessUnitName, storeName = x.StoreName, currentYearValue = x.CurrentYearValue, lastYearValue = x.LastYearValue, growthRate = x.GrowthRate }).ToList() }; } private async Task GetBuIndicatorStat(AnnualSummaryQueryInput input, Func fieldSelector) { int year = input.Year ?? DateTime.Now.Year; // 获取所有事业部(事业一部到事业六部) var allBusinessUnits = await _db.Queryable() .Where(x => x.Category == "department" && (x.DeleteMark == null || x.DeleteMark != 1)) .Where(x => x.FullName.Contains("事业") && x.FullName != "事业部") .OrderBy(x => x.FullName) .ToListAsync(); // 获取本年度数据 var currentData = await _db.Queryable() .Where(x => x.IsEffective == 1 && x.Year == year) .WhereIF(!string.IsNullOrEmpty(input.StoreName), x => x.StoreName.Contains(input.StoreName)) .ToListAsync(); // 获取上年度数据 var lastData = await _db.Queryable() .Where(x => x.IsEffective == 1 && x.Year == year - 1) .WhereIF(!string.IsNullOrEmpty(input.StoreName), x => x.StoreName.Contains(input.StoreName)) .ToListAsync(); // 创建事业部ID到名称的映射 var buIdToNameMap = allBusinessUnits.ToDictionary(x => x.Id, x => x.FullName); // 获取年度汇总数据中有数据的事业部ID(从BusinessUnitId或BusinessUnitName中获取) var dataBuIds = currentData .Where(x => !string.IsNullOrEmpty(x.BusinessUnitId)) .Select(x => x.BusinessUnitId) .Distinct() .ToList(); // 如果BusinessUnitId为空,尝试从BusinessUnitName中解析(可能是ID) var dataBuNames = currentData .Where(x => string.IsNullOrEmpty(x.BusinessUnitId) && !string.IsNullOrEmpty(x.BusinessUnitName)) .Select(x => x.BusinessUnitName) .Distinct() .ToList(); // 合并所有有数据的事业部ID var allDataBuIds = dataBuIds.Union(dataBuNames).Distinct().ToList(); var output = new IndicatorStatOutput(); // 遍历所有事业部(确保显示所有6个事业部) foreach (var bu in allBusinessUnits) { // 查找该事业部的数据(通过BusinessUnitId或BusinessUnitName匹配) var buCurrData = currentData .Where(x => (x.BusinessUnitId == bu.Id || x.BusinessUnitName == bu.Id || x.BusinessUnitName == bu.FullName)) .ToList(); var buLastData = lastData .Where(x => (x.BusinessUnitId == bu.Id || x.BusinessUnitName == bu.Id || x.BusinessUnitName == bu.FullName)) .ToList(); var curVal = buCurrData.Sum(x => fieldSelector(x)); var lastVal = buLastData.Sum(x => fieldSelector(x)); // 对于事业部汇总,使用汇总数据的增长率计算方法(基于实际有数据的月份) var growthRate = CalculateAggregateGrowthRateByAverage(buCurrData, buLastData, year, fieldSelector); output.Rows.Add(new IndicatorDataRow { BusinessUnitName = bu.FullName, // 使用事业部名称而不是ID StoreName = "汇总", CurrentYearValue = curVal, LastYearValue = lastVal, GrowthRate = growthRate }); } return new { rows = output.Rows.Select(x => new { businessUnitName = x.BusinessUnitName, storeName = x.StoreName, currentYearValue = x.CurrentYearValue, lastYearValue = x.LastYearValue, growthRate = x.GrowthRate }).ToList() }; } private string CalculateGrowthRate(decimal current, decimal last) { if (last == 0) return current > 0 ? "100%" : "0%"; var rate = (current - last) / last; return (rate * 100).ToString("F2") + "%"; } /// /// 批量获取门店开业时间 /// private async Task> GetStoreOpenTimesAsync(List storeIds) { if (storeIds == null || !storeIds.Any()) return new Dictionary(); var stores = await _db.Queryable() .Where(x => storeIds.Contains(x.Id)) .Select(x => new { x.Id, x.Kysj }) .ToListAsync(); return stores.ToDictionary(x => x.Id, x => x.Kysj); } /// /// 计算月均(考虑开业时间和实际有数据的月份) /// private decimal CalculateMonthlyAverage( List data, DateTime? openTime, int year, Func fieldSelector) { if (data == null || !data.Any()) return 0; // 获取实际有数据的月份(排除值为0或null的数据) var validData = data.Where(x => fieldSelector(x) > 0).ToList(); if (!validData.Any()) return 0; // 如果有开业时间且是开业当年,排除开业当月 if (openTime != null && openTime.Value.Year == year) { int openMonth = openTime.Value.Month; validData = validData.Where(x => x.Month > openMonth).ToList(); if (!validData.Any()) return 0; } // 统计实际有数据的月份数(去重) int actualMonthCount = validData.Select(x => x.Month).Distinct().Count(); if (actualMonthCount == 0) return 0; decimal total = validData.Sum(x => fieldSelector(x)); return total / actualMonthCount; } /// /// 计算汇总数据的月均(用于事业部汇总,考虑所有门店的实际营业月份) /// private decimal CalculateAggregateMonthlyAverage( List data, Func fieldSelector) { if (data == null || !data.Any()) return 0; // 获取实际有数据的月份(排除值为0或null的数据) var validData = data.Where(x => fieldSelector(x) > 0).ToList(); if (!validData.Any()) return 0; // 统计实际有数据的月份数(去重,因为可能有多个门店在同一个月有数据) int actualMonthCount = validData.Select(x => x.Month).Distinct().Count(); if (actualMonthCount == 0) return 0; decimal total = validData.Sum(x => fieldSelector(x)); return total / actualMonthCount; } /// /// 计算增长率(基于月均,考虑开业时间) /// private string CalculateGrowthRateByAverage( List currentData, List lastData, DateTime? openTime, int currentYear, Func fieldSelector) { // 25年新开门店,增长率 = 0 if (openTime != null && openTime.Value.Year == 2025) { return "0%"; } // 计算本年月均 decimal currentAvg = CalculateMonthlyAverage(currentData, openTime, currentYear, fieldSelector); // 计算上年月均 decimal lastAvg = CalculateMonthlyAverage(lastData, openTime, currentYear - 1, fieldSelector); if (lastAvg == 0) return currentAvg > 0 ? "100%" : "0%"; var rate = (currentAvg - lastAvg) / lastAvg; return (rate * 100).ToString("F2") + "%"; } /// /// 计算汇总数据的增长率(基于月均,用于事业部汇总) /// private string CalculateAggregateGrowthRateByAverage( List currentData, List lastData, int currentYear, Func fieldSelector) { // 计算本年月均(基于实际有数据的月份) decimal currentAvg = CalculateAggregateMonthlyAverage(currentData, fieldSelector); // 计算上年月均(基于实际有数据的月份) decimal lastAvg = CalculateAggregateMonthlyAverage(lastData, fieldSelector); if (lastAvg == 0) return currentAvg > 0 ? "100%" : "0%"; var rate = (currentAvg - lastAvg) / lastAvg; return (rate * 100).ToString("F2") + "%"; } /// /// 获取事业部ID到名称的映射 /// private async Task> GetBusinessUnitNameMapAsync() { var orgs = await _db.Queryable() .Where(x => x.Category == "department" && (x.DeleteMark == null || x.DeleteMark != 1)) .Select(x => new { x.Id, x.FullName }) .ToListAsync(); var map = new Dictionary(); foreach (var org in orgs) { map[org.Id] = org.FullName; // 如果名称本身也是ID格式,也添加映射 if (!map.ContainsKey(org.FullName)) { map[org.FullName] = org.FullName; } } return map; } /// /// 将事业部ID或名称转换为显示名称 /// private string GetBusinessUnitDisplayName(string buIdOrName, Dictionary buNameMap) { if (string.IsNullOrEmpty(buIdOrName)) return "未知"; // 如果映射中存在,返回名称 if (buNameMap.ContainsKey(buIdOrName)) return buNameMap[buIdOrName]; // 如果本身就是名称(包含"事业"或"科技"等),直接返回 if (buIdOrName.Contains("事业") || buIdOrName.Contains("科技") || buIdOrName.Contains("教育") || buIdOrName.Contains("大项目")) return buIdOrName; // 否则返回原值(可能是ID) return buIdOrName; } /// /// 获取Excel单元格的值(支持公式计算值) /// /// 单元格 /// 单元格的值(字符串形式) private string GetCellValue(ICell cell) { if (cell == null) return string.Empty; try { // 如果是公式类型,先尝试获取计算后的值 if (cell.CellType == CellType.Formula) { try { var formulaEvaluator = cell.Sheet.Workbook.GetCreationHelper().CreateFormulaEvaluator(); var cellValue = formulaEvaluator.Evaluate(cell); // 根据计算结果类型返回相应的值 switch (cellValue.CellType) { case CellType.String: return cellValue.StringValue?.Trim() ?? string.Empty; case CellType.Numeric: if (DateUtil.IsCellDateFormatted(cell)) { return cellValue.NumberValue.ToString("yyyy/MM/dd"); } var numValue = cellValue.NumberValue; if (numValue == Math.Floor(numValue)) { return ((long)numValue).ToString(); } return numValue.ToString(); case CellType.Boolean: return cellValue.BooleanValue.ToString(); case CellType.Error: // 公式计算错误,返回空字符串 return string.Empty; default: return string.Empty; } } catch { // 如果公式计算失败,尝试读取公式文本 // 如果包含公式特征,返回空字符串(会被后续逻辑跳过) var formulaText = cell.CellFormula; if (!string.IsNullOrEmpty(formulaText) && (formulaText.Contains("XLOOKUP") || formulaText.Contains("VLOOKUP") || formulaText.Contains("_xlfn.") || formulaText.StartsWith("="))) { return string.Empty; } return string.Empty; } } // 非公式类型,直接读取值 switch (cell.CellType) { case CellType.String: return cell.StringCellValue?.Trim() ?? string.Empty; case CellType.Numeric: if (DateUtil.IsCellDateFormatted(cell)) { return cell.DateCellValue.ToString("yyyy/MM/dd"); } // 处理数字,避免科学计数法 var numericValue = cell.NumericCellValue; if (numericValue == Math.Floor(numericValue)) { return ((long)numericValue).ToString(); } return numericValue.ToString(); case CellType.Boolean: return cell.BooleanCellValue.ToString(); case CellType.Blank: return string.Empty; default: return cell.ToString()?.Trim() ?? string.Empty; } } catch { return string.Empty; } } #endregion } }