作者:holdengong 鏈接:https://www.cnblogs.com/holdengong/p/10889838.html
簡介Excel和Word操作在開發(fā)過程中經(jīng)常需要使用,這類工作不涉及到核心業(yè)務(wù),但又往往不可缺少。以往的開發(fā)方式在業(yè)務(wù)代碼中直接引入NPOI、Aspose或者其他第三方庫,工作繁瑣,耗時(shí)多,擴(kuò)展性差——比如基礎(chǔ)庫由NPOI修改為EPPlus,意味著業(yè)務(wù)代碼需要全部修改。 由于工作需要,我在之前版本的基礎(chǔ)上,封裝了OfficeService,目的是最大化節(jié)省導(dǎo)入導(dǎo)出這種非核心功能開發(fā)時(shí)間,專注于業(yè)務(wù)實(shí)現(xiàn),并且業(yè)務(wù)端與底層基礎(chǔ)組件完全解耦,即業(yè)務(wù)端完全不需要知道底層使用的是什么基礎(chǔ)庫,使得重構(gòu)代價(jià)大大降低。 EasyOffice提供了 Excel導(dǎo)入:通過對(duì)模板類標(biāo)記特性自動(dòng)校驗(yàn)數(shù)據(jù)(后期計(jì)劃支持FluentApi,即傳參決定校驗(yàn)行為),并將有效數(shù)據(jù)轉(zhuǎn)換為指定類型,業(yè)務(wù)端只在拿到正確和錯(cuò)誤數(shù)據(jù)后決定如何處理; Excel導(dǎo)出:通過對(duì)模板類標(biāo)記特性自動(dòng)渲染樣式(后期計(jì)劃支持FluentApi,即傳參決定導(dǎo)出行為); Word根據(jù)模板生成:支持使用文本和圖片替換,占位符只需定義模板類,制作Word模板,一行代碼導(dǎo)出docx文檔(后期計(jì)劃支持轉(zhuǎn)換為pdf); Word根據(jù)Table母版生成:只需定義模板類,制作表格模板,傳入數(shù)據(jù),服務(wù)會(huì)根據(jù)數(shù)據(jù)條數(shù)自動(dòng)復(fù)制表格母版,并填充數(shù)據(jù); Word從空白創(chuàng)建等功能:特別復(fù)雜的Word導(dǎo)出任務(wù),支持從空白創(chuàng)建;
EasyOffice底層庫目前使用NPOI,因此是完全免費(fèi)的。 通過IExcelImportProvider等Provider接口實(shí)現(xiàn)了底層庫與實(shí)現(xiàn)的解耦,后期如果需要切換比如Excel導(dǎo)入的基礎(chǔ)庫為EPPlus,只需要提供IExcelImportProvider接口的EPPlus實(shí)現(xiàn),并且修改依賴注入代碼即可。 依賴注入提供了.NET Core自帶ServiceCollection注入和Autofac注入 // Autofac注入Office基礎(chǔ)服務(wù) builder.AddOffice(new OfficeOptions());
//.NET Core自帶ServiceCollection注入 services.AddOffice(new OfficeOptions());
IExcelImportService - Excel通用導(dǎo)入定義Excel模板類 public class Car { [ColName('車牌號(hào)')] //對(duì)應(yīng)Excel列名 [Required] //校驗(yàn)必填 [Regex(RegexConstant.CAR_CODE_REGEX)] //正則表達(dá)式校驗(yàn),RegexConstant預(yù)置了一些常用的正則表達(dá)式,也可以自定義 [Duplication] //校驗(yàn)?zāi)0孱愒摿袛?shù)據(jù)是否重復(fù) public string CarCode { get; set; }
[ColName('手機(jī)號(hào)')] [Regex(RegexConstant.MOBILE_CHINA_REGEX)] public string Mobile { get; set; }
[ColName('身份證號(hào)')] [Regex(RegexConstant.IDENTITY_NUMBER_REGEX)] public string IdentityNumber { get; set; }
[ColName('姓名')] [MaxLength(10)] //最大長度校驗(yàn) public string Name { get; set; }
[ColName('性別')] [Regex(RegexConstant.GENDER_REGEX)] public GenderEnum Gender { get; set; }
[ColName('注冊(cè)日期')] [DateTime] //日期校驗(yàn) public DateTime RegisterDate { get; set; }
[ColName('年齡')] [Range(0, 150)] //數(shù)值范圍校驗(yàn) public int Age { get; set; } }
校驗(yàn)數(shù)據(jù) var _rows = _excelImportService.ValidateAsync<ExcelCarTemplateDTO>(new ImportOption() { FileUrl = fileUrl, //Excel文件絕對(duì)地址 DataRowStartIndex = 1, //數(shù)據(jù)起始行索引,默認(rèn)1第二行 HeaderRowIndex = 0, //表頭起始行索引,默認(rèn)0第一行 MappingDictionary = null, //映射字典,可以將模板類與Excel列重新映射, 默認(rèn)null SheetIndex = 0, //頁面索引,默認(rèn)0第一個(gè)頁簽 ValidateMode = ValidateModeEnum.Continue //校驗(yàn)?zāi)J?,默認(rèn)StopOnFirstFailure校驗(yàn)錯(cuò)誤后此行停止繼續(xù)校驗(yàn),Continue:校驗(yàn)錯(cuò)誤后繼續(xù)校驗(yàn) }).Result;
//得到錯(cuò)誤行 var errorDatas = _rows.Where(x => !x.IsValid); //錯(cuò)誤行業(yè)務(wù)處理
//將有效數(shù)據(jù)行轉(zhuǎn)換為指定類型 var validDatas = _rows.Where(x=>x.IsValid).FastConvert<ExcelCarTemplateDTO>();
//正確數(shù)據(jù)業(yè)務(wù)處理
轉(zhuǎn)換為DataTable var dt = _excelImportService.ToTableAsync<ExcelCarTemplateDTO> //模板類型 ( fileUrl, //文件絕對(duì)地址 0, //頁簽索引,默認(rèn)0 0, //表頭行索引,默認(rèn)0 1, //數(shù)據(jù)行索引,默認(rèn)1 -1); //讀取多少條數(shù)據(jù),默認(rèn)-1全部 IExcelExportService - 通用Excel導(dǎo)出服務(wù)
IExcelExportService - 通用Excel導(dǎo)出服務(wù)定義導(dǎo)出模板類 [Header(Color = ColorEnum.BRIGHT_GREEN, FontSize = 22, IsBold = true)] //表頭樣式 [WrapText] //自動(dòng)換行 public class ExcelCarTemplateDTO { [ColName('車牌號(hào)')] [MergeCols] //相同數(shù)據(jù)自動(dòng)合并單元格 public string CarCode { get; set; }
[ColName('手機(jī)號(hào)')] public string Mobile { get; set; }
[ColName('身份證號(hào)')] public string IdentityNumber { get; set; }
[ColName('姓名')] public string Name { get; set; }
[ColName('性別')] public GenderEnum Gender { get; set; }
[ColName('注冊(cè)日期')] public DateTime RegisterDate { get; set; }
[ColName('年齡')] public int Age { get; set; }
導(dǎo)出Excel var bytes = await _excelExportService.ExportAsync(new ExportOption<ExcelCarTemplateDTO>() { Data = list, DataRowStartIndex = 1, //數(shù)據(jù)行起始索引,默認(rèn)1 ExcelType = Bayantu.Extensions.Office.Enums.ExcelTypeEnum.XLS,//導(dǎo)出Excel類型,默認(rèn)xls HeaderRowIndex = 0, //表頭行索引,默認(rèn)0 SheetName = 'sheet1' //頁簽名稱,默認(rèn)sheet1 });
File.WriteAllBytes(@'c: est.xls', bytes);
IExcelImportSolutionService - Excel導(dǎo)入解決方案服務(wù)(與前端控件配套的完整解決方案,可忽略) 首先定義模板類,參考通用Excel導(dǎo)入 //獲取默認(rèn)導(dǎo)入模板 var templateBytes = await _excelImportSolutionService.GetImportTemplateAsync<DemoTemplateDTO>();
//獲取導(dǎo)入配置 var importConfig = await _excelImportSolutionService.GetImportConfigAsync<DemoTemplateDTO>('uploadUrl','templateUrl'); //獲取預(yù)覽數(shù)據(jù) var previewData = await _excelImportSolutionService.GetFileHeadersAndRowsAsync<DemoTemplateDTO>('fileUrl'); //導(dǎo)入 var importOption = new ImportOption() { FileUrl = 'fileUrl', ValidateMode = ValidateModeEnum.Continue }; object importSetData = new object(); //前端傳過來的映射數(shù)據(jù) var importResult = await _excelImportSolutionService.ImportAsync<DemoTemplateDTO> (importOption , importSetData , BusinessAction //業(yè)務(wù)方法委托 , CustomValidate //自定義校驗(yàn)委托 ); //獲取導(dǎo)入錯(cuò)誤消息 var errorMsg = await _excelImportSolutionService.ExportErrorMsgAsync(importResult.Tag);
IWordExportService - Word通用導(dǎo)出服務(wù)CreateFromTemplateAsync - 根據(jù)模板生成Word //step1 - 定義模板類 public class WordCarTemplateDTO { //默認(rèn)占位符為{PropertyName} public string OwnerName { get; set; }
[Placeholder('{Car_Type Car Type}')] //重寫占位符 public string CarType { get; set; }
//使用Picture或IEnumerable<Picture>類型可以將占位符替換為圖片 public IEnumerable<Picture> CarPictures { get; set; }
public Picture CarLicense { get; set; } }
//step2 - 制作word模板 //step3 - 導(dǎo)出word string templateUrl = @'c: emplate.docx'; WordCarTemplateDTO car = new WordCarTemplateDTO() { OwnerName = '劉德華', CarType = '豪華型賓利', CarPictures = new List<Picture>() { new Picture() { PictureUrl = pic1, //圖片絕對(duì)地址,如果設(shè)置了PictureData此項(xiàng)不生效 FileName = '圖片1',//文件名稱 Height = 10,//圖片高度單位厘米默認(rèn)8 Width = 3,//圖片寬度單位厘米默認(rèn)14 PictureData = null,//圖片流數(shù)據(jù),優(yōu)先取這里的數(shù)據(jù),沒有則取url PictureType = PictureTypeEnum.JPEG //圖片類型,默認(rèn)jpeg }, new Picture(){ PictureUrl = pic2 } }, CarLicense = new Picture { PictureUrl = pic3 } };
var word = await _wordExportService.CreateFromTemplateAsync(templateUrl, car); File.WriteAllBytes(@'c:ile.docx', word.WordBytes);
CreateWordFromMasterTable-根據(jù)模板表格循環(huán)生成word //step1 - 定義模板類,參考上面 //step2 - 定義word模板,制作一個(gè)表格,填好占位符。 //step3 - 調(diào)用,如下示例,最終生成的word有兩個(gè)用戶表格 string templateurl = @'c: emplate.docx'; var user1 = new UserInfoDTO() { Name = '張三', Age = 15, Gender = '男', Remarks = '簡介簡介' }; var user2 = new UserInfoDTO() { Name = '李四', Age = 20, Gender = '女', Remarks = '簡介簡介簡介' };
var datas = new List<UserInfoDTO>() { user1, user2 };
for (int i = 0; i < 10; i++) { datas.Add(user1); datas.Add(user2); } var word = await _wordExportService.CreateFromMasterTableAsync(templateurl, datas); File.WriteAllBytes(@'c:ile.docx', word.WordBytes);
CreateWordAsync - 從空白生成word//step1 - 定義模板類,參考上面 //step2 - 定義word模板,制作一個(gè)表格,填好占位符。 //step3 - 調(diào)用,如下示例,最終生成的word有兩個(gè)用戶表格 string templateurl = @'c: emplate.docx'; var user1 = new UserInfoDTO() { Name = '張三', Age = 15, Gender = '男', Remarks = '簡介簡介' }; var user2 = new UserInfoDTO() { Name = '李四', Age = 20, Gender = '女', Remarks = '簡介簡介簡介' };
var datas = new List<UserInfoDTO>() { user1, user2 }; for (int i = 0; i < 10; i++) { datas.Add(user1); datas.Add(user2); } var word = await _wordExportService.CreateFromMasterTableAsync(templateurl, datas);
File.WriteAllBytes(@'c:ile.docx', word.WordBytes); CreateWordAsync - 從空白生成word [Fact] public async Task 導(dǎo)出所有日程() { //準(zhǔn)備數(shù)據(jù) var date1 = new ScheduleDate() { DateTimeStr = '2019年5月5日 星期八', Addresses = new List<Address>() };
var address1 = new Address() { Name = '會(huì)場一', Categories = new List<Category>() };
var cate1 = new Category() { Name = '分類1', Schedules = new List<Schedule>() };
var schedule1 = new Schedule() { Name = '日程1', TimeString = '上午9:00 - 上午12:00', Speakers = new List<Speaker>() };
var schedule2 = new Schedule() { Name = '日程2', TimeString = '下午13:00 - 下午14:00', Speakers = new List<Speaker>() };
var speaker1 = new Speaker() { Name = '張三', Position = '總經(jīng)理' }; var speaker2 = new Speaker() { Name = '李四', Position = '副總經(jīng)理' }; schedule1.Speakers.Add(speaker1); schedule1.Speakers.Add(speaker2); cate1.Schedules.Add(schedule1); cate1.Schedules.Add(schedule2); address1.Categories.Add(cate1); date1.Addresses.Add(address1); var dates = new List<ScheduleDate>() { date1,date1,date1 }; var tables = new List<Table>(); //新建一個(gè)表格 var table = new Table() { Rows = new List<TableRow>() }; foreach (var date in dates) { //新建一行 var rowDate = new TableRow() { Cells = new List<TableCell>() }; //新增單元格 rowDate.Cells.Add(new TableCell() { Color = 'lightblue', //設(shè)置單元格顏色 Paragraphs = new List<Paragraph>() { //新增段落 new Paragraph() { //段落里面新增文本域 Run = new Run() { Text = date.DateTimeStr,//文本域文本,Run還可以 Color = 'red', //設(shè)置文本顏色 FontFamily = '微軟雅黑',//設(shè)置文本字體 FontSize = 12,//設(shè)置文本字號(hào) IsBold = true,//是否粗體 Pictures = new List<Picture>()//也可以插入圖片 }, Alignment = Alignment.CENTER //段落居中 } } }); table.Rows.Add(rowDate); //會(huì)場 foreach (var addr in date.Addresses) { //分類 foreach (var cate in addr.Categories) { var rowCate = new TableRow() { Cells = new List<TableCell>() };
//會(huì)場名稱 rowCate.Cells.Add(new TableCell() { Paragraphs = new List<Paragraph>{ new Paragraph() { Run = new Run() { Text = addr.Name, } } } }); rowCate.Cells.Add(new TableCell() { Paragraphs = new List<Paragraph>(){ new Paragraph() { Run = new Run() { Text = cate.Name, } } } }); table.Rows.Add(rowCate); //日程 foreach (var sche in cate.Schedules) { var rowSche = new TableRow() { Cells = new List<TableCell>() }; var scheCell = new TableCell() { Paragraphs = new List<Paragraph>() { new Paragraph() { Run = new Run() { Text = sche.Name } }, { new Paragraph() { Run = new Run() { Text = sche.TimeString } } } } }; foreach (var speaker in sche.Speakers) { scheCell.Paragraphs.Add(new Paragraph() { Run = new Run() { Text = $'{speaker.Position}:{speaker.Name}' } }); } rowSche.Cells.Add(scheCell); table.Rows.Add(rowSche); } } } } tables.Add(table); var word = await _wordExportService.CreateWordAsync(tables); File.WriteAllBytes(fileUrl, word.WordBytes); }
詳細(xì)例子請(qǐng)github下載代碼后自行跑單元測試查看 github地址:https://github.com/holdengong/EasyOffice
|