创建一个.NET Web API项目,这里使用的是.NET Core 3.1框架,导出Excel这里使用开源组件NPOI,常规的导入导出,肯定得不陌生,今天,我们讲一讲,复杂的单元格合并,样式如下:
可以看到,设备类型,这一块是一个合并的,那么在.NET Core中,如何实现呢?通过SQL语句,我们得到如下的数据:
- 我们首先第一步,查询出数据,记作【equipmentResps】
List<EquipmentResp> equipmentResps = _equipmentService.GetEquipmentResps().ToList();
- 使用NPOI手动创建一个Excel,引入NPOI,本次使用NPOI, Version=2.5.3.0
完整导出代码如下:
//创建Workbook IWorkbook workbook = new HSSFWorkbook(); //创建一个sheet workbook.CreateSheet("设备管理"); //设置一个随机用户名 string path = $"{Guid.NewGuid().ToString()}.xls"; //找到当前项目所在文件夹,需要注入IWebHostEnvironment string filePath = $"{_webHostEnvironment.ContentRootPath}\\Export\\"; if (!System.IO.Directory.Exists(filePath)) { Directory.CreateDirectory(filePath); } using (FileStream fs = System.IO.File.Create(filePath + path)) { ISheet sheet = workbook.GetSheetAt(0);//获取sheet IRow rowHeader = sheet.CreateRow(0); //创建头部 rowHeader.CreateCell(0).SetCellValue("设备类型"); rowHeader.CreateCell(1).SetCellValue("设备分类"); rowHeader.CreateCell(2).SetCellValue("设备数量(台)"); rowHeader.CreateCell(3).SetCellValue("完好设备数量(台)"); rowHeader.CreateCell(4).SetCellValue("完好率(%)"); //创建头部样式和列宽度 ICellStyle titleStyle = workbook.CreateCellStyle(); titleStyle.Alignment = HorizontalAlignment.Center; // 居中 IFont titleFont = workbook.CreateFont(); titleFont.IsBold = true; titleFont.FontHeightInPoints = 12; titleFont.Color = HSSFColor.Black.Index;//设置字体颜色 titleStyle.SetFont(titleFont); heet.GetRow(0).GetCell(0).CellStyle = titleStyle; sheet.GetRow(0).GetCell(1).CellStyle = titleStyle; sheet.GetRow(0).GetCell(2).CellStyle = titleStyle; sheet.GetRow(0).GetCell(4).CellStyle = titleStyle; sheet.SetColumnWidth(0, 3000); sheet.SetColumnWidth(1, 3000); sheet.SetColumnWidth(2, 5000); sheet.SetColumnWidth(3, 5000); sheet.SetColumnWidth(4, 4000); //创建内容样式 ICellStyle style = workbook.CreateCellStyle(); style.Alignment = HorizontalAlignment.Center; style.VerticalAlignment = VerticalAlignment.Center; ;//垂直居中 IFont font = workbook.CreateFont(); font.IsBold = false; font.FontHeightInPoints = 12; font.Color = HSSFColor.Black.Index; style.SetFont(font); //赋值 for (int i = 0; i < equipmentResps.ToList().Count(); i++) { IRow rowData = sheet.CreateRow(i + 1); rowData.CreateCell(0).SetCellValue(equipmentResps[i].type); if (!keyValues.ContainsKey(equipmentResps[i].type)) { keyValues.Add(equipmentResps[i].type, equipmentResps[i].type); } rowData.GetCell(0).CellStyle = style; rowData.CreateCell(1).SetCellValue(equipmentResps[i].Grade); rowData.GetCell(1).CellStyle = style; rowData.CreateCell(2).SetCellValue(equipmentResps[i].Number); rowData.GetCell(2).CellStyle = style; rowData.CreateCell(3).SetCellValue(equipmentResps[i].perfectNumber); rowData.GetCell(3).CellStyle = style; rowData.CreateCell(4).SetCellValue(equipmentResps[i].IntactRate.ToString() + "%"); rowData.GetCell(4).CellStyle = style; } foreach (var item in keyValues) { int start = equipmentResps.IndexOf(equipmentResps.FirstOrDefault(x => x.type == item.Key)); int end = equipmentResps.IndexOf(equipmentResps.LastOrDefault(x => x.type == item.Key)); sheet.AddMergedRegion(new CellRangeAddress(start + 1, end + 1, 0, 0)); } MemoryStream ms = new MemoryStream(); workbook.Write(ms); ms.Seek(0, SeekOrigin.Begin); string fileName = $"{DateTime.Now.ToString("yyyy-MM")}设备完好率.xls"; return File(ms, "application/vnd.ms-excel", fileName); }
- 合并单元格子解析
通过NPOI官网,我们可以看到,合并单元格,需要使用AddMergedRegion函数,AddMergedRegion有四个参数,
分别是:【开始行、结束行、开始列、结束列】,其实很好记,就是从哪一行开始,合并到哪一行,包含那些列,通过对上面excel分析,我们得出【电气设备】在第2行,记作N,需要合并3行,即N+1,其余需要合并的以此类推,我们可以通过代码做如下分析:
// Dictionary<string, string> keyValues = new Dictionary<string, string>();
//keyValues 里面其实只记录了type的值,通过循环这个值,进行合并
foreach (var item in keyValues)
{
//IndexOf 找到列表出现相同信息的下标,使用FirstOrDefault是为了找到第一次出现此数据的行号
int start = equipmentResps.IndexOf(equipmentResps.FirstOrDefault(x => x.type == item.Key));
//同样的方式,通过LastOrDefault是为了找到第=最后一次出现此数据的行号
int end = equipmentResps.IndexOf(equipmentResps.LastOrDefault(x => x.type == item.Key));
//然后使用NPOI合并的方式进行合并,因为本次我们都是出现在第一列(excel从0开始),所以后面两个参数都
//是0
sheet.AddMergedRegion(new CellRangeAddress(start + 1, end + 1, 0, 0));
}
前端可以使用<a>标签,比如【<a href='我们部署的接口地址'>导出</a>】可直接导出,也可以使用请求接口,然后回调的方式进行导出。