创建一个.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>】可直接导出,也可以使用请求接口,然后回调的方式进行导出。