一、前言
最近項(xiàng)目開發(fā)中,有一個(gè)根據(jù)word模板和指定的數(shù)據(jù)導(dǎo)出word文件的需求,word模板文件如下,需要將指定標(biāo)簽替換為數(shù)據(jù)中指定的字段,表格根據(jù)第一行的標(biāo)簽生成列表數(shù)據(jù),將指定的標(biāo)簽替換為圖片,word模板如下
二、開發(fā)準(zhǔn)備,為了做成一個(gè)通用的word導(dǎo)出功能,只需要提供模板以及數(shù)據(jù)實(shí)體就可以實(shí)現(xiàn)導(dǎo)出不一樣的數(shù)據(jù),項(xiàng)目中主要使用以下Nuget包
1、DocumentFormat.OpenXml 用于word文件修改編輯操作
2、BarcodeLib 用于生成條碼圖片
3、SkiaSharp 用于生成跨平臺(tái)的圖片格式
三、word模板文件編輯,word模板文件圖片如下,請注意:標(biāo)簽中不要換行加tab符等,否則讀取模板文件時(shí)標(biāo)簽一個(gè)標(biāo)簽可能會(huì)變成多個(gè)標(biāo)簽

四、word生成幫助類,該類主要功能,根據(jù)指定的數(shù)據(jù)和模板生成word文件并且直接下載,如果需要保存可以指定保存路徑,代碼如下:
using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Drawing; using DocumentFormat.OpenXml.Drawing.Wordprocessing; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; using System.Reflection; using Run = DocumentFormat.OpenXml.Wordprocessing.Run; using Table = DocumentFormat.OpenXml.Wordprocessing.Table; using TableCell = DocumentFormat.OpenXml.Wordprocessing.TableCell; using TableGrid = DocumentFormat.OpenXml.Wordprocessing.TableGrid; using TableProperties = DocumentFormat.OpenXml.Wordprocessing.TableProperties; using TableRow = DocumentFormat.OpenXml.Wordprocessing.TableRow; using Text = DocumentFormat.OpenXml.Wordprocessing.Text; namespace Core.Office { public class WordHelper { #region 根據(jù)模板生成文件合集 /// <summary> /// 根據(jù)指定實(shí)體類和模板生成Word文檔,替換模板中的標(biāo)簽和圖片 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="templatePath"></param> /// <param name="data"></param> /// <returns></returns> public static Stream GenerateDocumentFromTemplate<T>( string templatePath, T data) { var filebytes = FileUtils.GetFileContentBytes(templatePath); var stream = new MemoryStream(filebytes); // 打開文檔進(jìn)行修改 using(WordprocessingDocument doc = WordprocessingDocument.Open(stream, true)) { // 生成替換字典 Dictionary<string, string> replacements = GenerateReplacements(data); // 替換主文檔內(nèi)容 ReplaceTextInPart(doc.MainDocumentPart, replacements); // 替換頁眉 foreach(var headerPart in doc.MainDocumentPart.HeaderParts) { ReplaceTextInPart(headerPart, replacements); } // 替換頁腳 foreach(var footerPart in doc.MainDocumentPart.FooterParts) { ReplaceTextInPart(footerPart, replacements); } Dictionary<string, Tuple<string, Stream, double, double>> imageReplacements = GenerateReplacementImages(data); if(imageReplacements != null && imageReplacements.Count > 0) { foreach(var item in imageReplacements) { ReplaceTextWithImage(doc.MainDocumentPart, item.Key, item.Value.Item2, item.Value.Item1, item.Value.Item3, item.Value.Item4); } } // 確保所有更改已保存 doc.Save(); stream.Position = 0; // 重置流位置以便后續(xù)讀取 return stream; } } /// <summary> /// 根據(jù)模板文件(路徑)根據(jù)實(shí)體配置替換標(biāo)簽,圖片以及列表數(shù)據(jù)(1個(gè)列表) /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="T1"></typeparam> /// <param name="templatePath"></param> /// <param name="outputPath"></param> /// <param name="data"></param> /// <param name="tabledata"></param> /// <param name="tableIndex"></param> public static Stream GenerateDocumentFromTemplate<T, T1>( string templatePath, T data, List<T1> tabledata, int tableIndex = 0) { var filebytes = FileUtils.GetFileContentBytes(templatePath); var stream = new MemoryStream(filebytes); // 打開文檔進(jìn)行修改 using(WordprocessingDocument doc = WordprocessingDocument.Open(stream, true)) { // 生成替換字典 Dictionary<string, string> replacements = GenerateReplacements(data); // 調(diào)用重載方法 // 替換主文檔內(nèi)容 ReplaceTextInPart(doc.MainDocumentPart, replacements); // 替換頁眉 foreach(var headerPart in doc.MainDocumentPart.HeaderParts) { ReplaceTextInPart(headerPart, replacements); } // 替換頁腳 foreach(var footerPart in doc.MainDocumentPart.FooterParts) { ReplaceTextInPart(footerPart, replacements); } if(!tabledata.IsEmpty()) { GenerateTableRows<T1>(doc.MainDocumentPart, tabledata, tableIndex); } Dictionary<string, Tuple<string, Stream, double, double>> imageReplacements = GenerateReplacementImages(data); if(imageReplacements != null && imageReplacements.Count > 0) { foreach(var item in imageReplacements) { ReplaceTextWithImage(doc.MainDocumentPart, item.Key, item.Value.Item2, item.Value.Item1, item.Value.Item3, item.Value.Item4); } } if(!tabledata.IsEmpty()) { GenerateTableRows<T1>(doc.MainDocumentPart, tabledata, tableIndex); } doc.Save(); // 確保所有更改已保存 stream.Position = 0; // 重置流位置以便后續(xù)讀取 return stream; } } /// <summary> /// 根據(jù)模板文件(路徑)根據(jù)實(shí)體配置替換標(biāo)簽,圖片以及列表數(shù)據(jù)(2個(gè)列表) /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="T1"></typeparam> /// <typeparam name="T2"></typeparam> /// <param name="templatePath"></param> /// <param name="data"></param> /// <param name="tabledata"></param> /// <param name="tableIndex"></param> /// <param name="tabledata1"></param> /// <param name="tableIndex1"></param> public static byte[] GenerateDocumentFromTemplate<T, T1, T2>( string templatePath, T data, List<T1> tabledata, int tableIndex = 0, List<T2> tabledata1 = null, int tableIndex1 = 1) { // 1. 從服務(wù)器讀取模板文件 using var fileStream = new FileStream(FileUtils.GetMapPath(templatePath), FileMode.Open, FileAccess.Read); // 2. 創(chuàng)建內(nèi)存流并復(fù)制模板內(nèi)容 using var memoryStream = new MemoryStream(); fileStream.CopyTo(memoryStream); // 3. 使用OpenXML處理文檔 memoryStream.Position = 0; // 重置流位置; // 打開文檔進(jìn)行修改 using(WordprocessingDocument doc = WordprocessingDocument.Open(memoryStream, true)) { // 生成替換字典 Dictionary<string, string> replacements = GenerateReplacements(data); // 調(diào)用重載方法 // 替換主文檔內(nèi)容 ReplaceTextInPart(doc.MainDocumentPart, replacements); // 替換頁眉 foreach(var headerPart in doc.MainDocumentPart.HeaderParts) { ReplaceTextInPart(headerPart, replacements); } // 替換頁腳 foreach(var footerPart in doc.MainDocumentPart.FooterParts) { ReplaceTextInPart(footerPart, replacements); } Dictionary<string, Tuple<string, Stream, double, double>> imageReplacements = GenerateReplacementImages(data); if(imageReplacements != null && imageReplacements.Count > 0) { foreach(var item in imageReplacements) { ReplaceTextWithImage(doc.MainDocumentPart, item.Key, item.Value.Item2, item.Value.Item1, item.Value.Item3, item.Value.Item4); } } if(!tabledata.IsEmpty()) { GenerateTableRows<T1>(doc.MainDocumentPart, tabledata, tableIndex); } if(!tabledata1.IsEmpty()) { GenerateTableRows<T2>(doc.MainDocumentPart, tabledata1, tableIndex1); } } memoryStream.Position = 0; // 再次重置流位置 return memoryStream.ToArray(); } /// <summary> /// 根據(jù)模板文件(路徑)根據(jù)實(shí)體配置替換標(biāo)簽,圖片 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="templatePath"></param> /// <param name="outputPath"></param> /// <param name="data"></param> public static void GenerateDocumentFromTemplate<T>( string templatePath, string outputPath, T data) { File.Copy(templatePath, outputPath, overwrite: true); // 打開文檔進(jìn)行修改 using(WordprocessingDocument doc = WordprocessingDocument.Open(outputPath, true)) { // 生成替換字典 Dictionary<string, string> replacements = GenerateReplacements(data); // 替換主文檔內(nèi)容 ReplaceTextInPart(doc.MainDocumentPart, replacements); // 替換頁眉 foreach(var headerPart in doc.MainDocumentPart.HeaderParts) { ReplaceTextInPart(headerPart, replacements); } // 替換頁腳 foreach(var footerPart in doc.MainDocumentPart.FooterParts) { ReplaceTextInPart(footerPart, replacements); } Dictionary<string, Tuple<string, Stream, double, double>> imageReplacements = GenerateReplacementImages(data); if(imageReplacements != null && imageReplacements.Count > 0) { foreach(var item in imageReplacements) { ReplaceTextWithImage(doc.MainDocumentPart, item.Key, item.Value.Item2, item.Value.Item1, item.Value.Item3, item.Value.Item4); } } } } /// <summary> /// 根據(jù)模板文件(路徑)根據(jù)實(shí)體配置替換標(biāo)簽,圖片以及列表數(shù)據(jù)(1個(gè)列表) /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="T1"></typeparam> /// <param name="templatePath"></param> /// <param name="outputPath"></param> /// <param name="data"></param> /// <param name="tabledata"></param> /// <param name="tableIndex"></param> public static void GenerateDocumentFromTemplate<T, T1>( string templatePath, string outputPath, T data, List<T1> tabledata, int tableIndex = 0) { File.Copy(templatePath, outputPath, overwrite: true); // 打開文檔進(jìn)行修改 using(WordprocessingDocument doc = WordprocessingDocument.Open(outputPath, true)) { // 生成替換字典 Dictionary<string, string> replacements = GenerateReplacements(data); // 調(diào)用重載方法 // 替換主文檔內(nèi)容 ReplaceTextInPart(doc.MainDocumentPart, replacements); // 替換頁眉 foreach(var headerPart in doc.MainDocumentPart.HeaderParts) { ReplaceTextInPart(headerPart, replacements); } // 替換頁腳 foreach(var footerPart in doc.MainDocumentPart.FooterParts) { ReplaceTextInPart(footerPart, replacements); } if(!tabledata.IsEmpty()) { GenerateTableRows<T1>(doc.MainDocumentPart, tabledata, tableIndex); } Dictionary<string, Tuple<string, Stream, double, double>> imageReplacements = GenerateReplacementImages(data); if(imageReplacements != null && imageReplacements.Count > 0) { foreach(var item in imageReplacements) { ReplaceTextWithImage(doc.MainDocumentPart, item.Key, item.Value.Item2, item.Value.Item1, item.Value.Item3, item.Value.Item4); } } if(!tabledata.IsEmpty()) { GenerateTableRows<T1>(doc.MainDocumentPart, tabledata, tableIndex); } } } /// <summary> /// 根據(jù)模板文件(路徑)根據(jù)實(shí)體配置替換標(biāo)簽,圖片以及列表數(shù)據(jù)(2個(gè)列表) /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="T1"></typeparam> /// <typeparam name="T2"></typeparam> /// <param name="templatePath"></param> /// <param name="outputPath"></param> /// <param name="data"></param> /// <param name="tabledata"></param> /// <param name="tableIndex"></param> /// <param name="tabledata1"></param> /// <param name="tableIndex1"></param> public static void GenerateDocumentFromTemplate<T, T1, T2>( string templatePath, string outputPath, T data, List<T1> tabledata, int tableIndex = 0, List<T2> tabledata1 = null, int tableIndex1 = 1) { File.Copy(templatePath, outputPath, overwrite: true); // 打開文檔進(jìn)行修改 using(WordprocessingDocument doc = WordprocessingDocument.Open(outputPath, true)) { // 生成替換字典 Dictionary<string, string> replacements = GenerateReplacements(data); // 調(diào)用重載方法 // 替換主文檔內(nèi)容 ReplaceTextInPart(doc.MainDocumentPart, replacements); // 替換頁眉 foreach(var headerPart in doc.MainDocumentPart.HeaderParts) { ReplaceTextInPart(headerPart, replacements); } // 替換頁腳 foreach(var footerPart in doc.MainDocumentPart.FooterParts) { ReplaceTextInPart(footerPart, replacements); } Dictionary<string, Tuple<string, Stream, double, double>> imageReplacements = GenerateReplacementImages(data); if(imageReplacements != null && imageReplacements.Count > 0) { foreach(var item in imageReplacements) { //ReplaceTextWithImage(doc.MainDocumentPart, item.Key, FileUtils.GetMapPath("/templates/默認(rèn)圖片.png"), item.Value.Item3, item.Value.Item4); ReplaceTextWithImage(doc.MainDocumentPart, item.Key, item.Value.Item2, item.Value.Item1, item.Value.Item3, item.Value.Item4); } } if(!tabledata.IsEmpty()) { GenerateTableRows<T1>(doc.MainDocumentPart, tabledata, tableIndex); } if(!tabledata1.IsEmpty()) { GenerateTableRows<T2>(doc.MainDocumentPart, tabledata1, tableIndex1); } } } /// <summary> /// 生成word文檔從模板文件,僅替換標(biāo)簽 /// </summary> /// <param name="templatePath"></param> /// <param name="outputPath"></param> /// <param name="replacements"></param> public static void GenerateDocumentFromTemplate( string templatePath, string outputPath, Dictionary<string, string> replacements) { // 復(fù)制模板到新位置 File.Copy(templatePath, outputPath, overwrite: true); // 打開文檔進(jìn)行修改 using(WordprocessingDocument doc = WordprocessingDocument.Open(outputPath, true)) { // 替換主文檔內(nèi)容 ReplaceTextInPart(doc.MainDocumentPart, replacements); // 替換頁眉 foreach(var headerPart in doc.MainDocumentPart.HeaderParts) { ReplaceTextInPart(headerPart, replacements); } // 替換頁腳 foreach(var footerPart in doc.MainDocumentPart.FooterParts) { ReplaceTextInPart(footerPart, replacements); } } } #endregion // 安全替換方法 - 避免破壞XML結(jié)構(gòu) private static void ReplaceTextInPart(OpenXmlPart part, Dictionary<string, string> replacements) { if(part == null || part.RootElement == null) return; // 遍歷所有文本元素 foreach(var text in part.RootElement.Descendants<Text>()) { Console.WriteLine(text.InnerText); foreach(var rep in replacements) { if(text.Text.Contains(rep.Key)) { text.Text = text.Text.Replace(rep.Key, rep.Value); } } } } // 可選:表格行生成示例 /// <summary> /// 表格行生成,用于在Word文檔中動(dòng)態(tài)添加表格行。 /// </summary> /// <param name="mainPart"></param> /// <param name="data">數(shù)據(jù)列表</param> /// <param name="tableIndex">表格索引</param> private static void GenerateTableRows<T>(MainDocumentPart mainPart, List<T> data, int tableIndex) { IEnumerable<TableGrid> TableGrids = mainPart.Document.Body.Descendants<TableGrid>(); // 獲取模板表格 IEnumerable<Table> tables = mainPart.Document.Body.Descendants<Table>(); if(tables.IsEmpty()) return; if(tables.Count() <= tableIndex) return; var table = tables.ElementAt(tableIndex); var grids = mainPart.Document.Body.Descendants<TableGrid>(); TableProperties tableProperties = new TableProperties(); TableWidth tableWidth = new TableWidth() { Width = "100%", Type = TableWidthUnitValues.Dxa }; tableProperties.Append(tableWidth); //var newtable = table.Clone(); // 獲取模板行(第二行作為模板) TableRow templateRow = table.Elements<TableRow>().ElementAt(1); // 移除模板行(保留表頭) templateRow.Remove(); // 每條記錄添加新行 foreach(var item in data) { TableRow newRow = (TableRow)templateRow.CloneNode(true); var dict = GenerateReplacements(item); if(dict == null || dict.Count == 0) { continue; // 如果沒有替換項(xiàng),則跳過 } // 替換行內(nèi)占位符 ReplaceTextInRow(newRow, dict); table.AppendChild(newRow); } } /// <summary> /// 根據(jù)實(shí)體生成替換字典 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="data"></param> /// <returns></returns> private static Dictionary<string, string> GenerateReplacements<T>(T data) { // 假設(shè)data是一個(gè)包含屬性的對(duì)象 var replacements = new Dictionary<string, string>(); foreach(var prop in typeof(T).GetProperties()) { try { var exportTemplate = RestorExportTempalteAttribute(prop); if(exportTemplate == null) continue; if(exportTemplate.IsImage) continue; // 如果是圖片類型,跳過 var value = prop.GetValue(data)?.ToString() ?? string.Empty; replacements.Add("{{" + exportTemplate.TemplateName + "}}", value); } catch(Exception ex) { Console.WriteLine(prop.Name); } } return replacements; } /// <summary> /// 根據(jù)實(shí)體生成圖片替換字典 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="data"></param> /// <returns></returns> private static Dictionary<string, Tuple<string, Stream, double, double>> GenerateReplacementImages<T>(T data) { // 假設(shè)data是一個(gè)包含屬性的對(duì)象 var replacements = new Dictionary<string, Tuple<string, Stream, double, double>>(); foreach(var prop in typeof(T).GetProperties()) { string filename = ""; var exportTemplate = RestorExportTempalteAttribute(prop); if(exportTemplate == null) continue; if(!exportTemplate.IsImage) continue; // 如果是圖片類型,跳過 Stream value = null; if(typeof(Stream).IsAssignableFrom(prop.PropertyType)) { filename = exportTemplate.TemplateName + ".png"; // 使用屬性名作為文件名 value = prop.GetValue(data) as Stream; } else { var imagePath = prop.GetValue(data)?.ToNullString(); filename = imagePath; if(File.Exists(imagePath)) { using(FileStream stream = new FileStream(imagePath, FileMode.Open)) { value = stream; // 獲取圖片流 } } } replacements.Add("{{" + exportTemplate.TemplateName + "}}", new Tuple<string, Stream, double, double>(filename, value, exportTemplate.ImageWidth, exportTemplate.ImageHeight)); } return replacements; } /// <summary> /// 填充自定義屬性的值 /// </summary> /// <param name="p"></param> /// <param name="value"></param> /// <returns></returns> public static ExportTemplateAttribute RestorExportTempalteAttribute(PropertyInfo p) { if(p == null) return null; MethodInfo mi = p.GetGetMethod(); if(mi == null) return null; object[] attrs = p.GetCustomAttributes(false); if(!attrs.IsEmpty()) { for(int i = 0; i < attrs.Length; i++)//循環(huán)自定義屬性 { if(attrs[i].GetType() == typeof(ExportTemplateAttribute)) { ExportTemplateAttribute gc = (ExportTemplateAttribute)attrs[i]; if(gc != null) { return gc; } } } } return null; } private static void ReplaceTextInRow(TableRow row, Dictionary<string, string> replacements) { foreach(var cell in row.Elements<TableCell>()) { foreach(var text in cell.Descendants<Text>()) { foreach(var rep in replacements) { if(text.Text.Contains(rep.Key)) { text.Text = text.Text.Replace(rep.Key, rep.Value); } } } } } /// <summary> /// 替換圖片 /// </summary> /// <param name="mainPart"></param> /// <param name="placeholder"></param> /// <param name="imageStream"></param> private static void ReplaceImage(MainDocumentPart mainPart, string placeholder, Stream imageStream) { var drawing = mainPart.Document.Descendants<Drawing>() .FirstOrDefault(d => d.InnerText.Contains(placeholder)); if(drawing != null && imageStream != null) { var imagePart = mainPart.AddImagePart(ImagePartType.Jpeg); imagePart.FeedData(imageStream); var blip = drawing.Descendants<Blip>().First(); blip.Embed = mainPart.GetIdOfPart(imagePart); } } /// <summary> /// 點(diǎn)位文本替換為圖片 /// </summary> /// <param name="mainPart"></param> /// <param name="placeholderText"></param> /// <param name="imagePath"></param> /// <param name="widthCm"></param> /// <param name="heightCm"></param> private static void ReplaceTextWithImage( MainDocumentPart mainPart, string placeholderText, Stream imageStream, string imageName, double widthCm, double heightCm) { if(imageStream == null) { Console.WriteLine($"警告:圖片文件不存在 - {imageName}"); return; } // 1. 查找包含占位符文本的所有Run元素 var runsWithPlaceholder = mainPart.Document .Descendants<Run>() .Where(run => run.InnerText.Contains(placeholderText)) .ToList(); if(!runsWithPlaceholder.Any()) { Console.WriteLine($"警告:未找到文本占位符 '{placeholderText}'"); return; } // 2. 準(zhǔn)備圖片部件 PartTypeInfo imagePartType = GetImagePartType(imageName.GetExt()); ImagePart imagePart = mainPart.AddImagePart(imagePartType); imagePart.FeedData(imageStream); string imageRelId = mainPart.GetIdOfPart(imagePart); // 3. 計(jì)算圖片尺寸(轉(zhuǎn)換為EMU單位) const long emuPerCm = 360000; // 1 cm = 360,000 EMUs long widthEmu = (long)(widthCm * emuPerCm); long heightEmu = (long)(heightCm * emuPerCm); // 4. 創(chuàng)建圖片元素 Drawing drawing = CreateImageElement(imageRelId, widthEmu, heightEmu, System.IO.Path.GetFileName(imageName)); foreach(var run in runsWithPlaceholder) { // 5. 移除原有文本 run.RemoveAllChildren<Text>(); // 6. 創(chuàng)建新的Run來包含圖片 Run newRun = new Run(); newRun.AppendChild(drawing.CloneNode(true)); // 7. 替換原有Run run.Parent.InsertAfter(newRun, run); } Console.WriteLine($"已替換文本 '{placeholderText}' 為圖片: {imageName} ({widthCm}cm x {heightCm}cm)"); } /// <summary> /// 創(chuàng)建圖片Drawing元素 /// </summary> /// <param name="relationshipId"></param> /// <param name="widthEmu"></param> /// <param name="heightEmu"></param> /// <param name="fileName"></param> /// <returns></returns> private static Drawing CreateImageElement(string relationshipId, long widthEmu, long heightEmu, string fileName) { return new Drawing( new Inline( new Extent { Cx = widthEmu, Cy = heightEmu }, new DocProperties { Id = 1U, Name = "barcode" }, new Graphic( new GraphicData( new DocumentFormat.OpenXml.Drawing.Pictures.Picture( new BlipFill( new Blip { Embed = relationshipId }, new Stretch() ), new ShapeProperties( new Transform2D( new Offset { X = 0, Y = 0 }, new Extents { Cx = widthEmu, Cy = heightEmu } ), new PresetGeometry { Preset = ShapeTypeValues.Rectangle } ) ) ) { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" } ) ) { DistanceFromTop = 0, DistanceFromBottom = 0, DistanceFromLeft = 0, DistanceFromRight = 0 } ); } /// <summary> /// 根據(jù)文件擴(kuò)展名獲取圖片類型 /// </summary> /// <param name="extension"></param> /// <returns></returns> private static PartTypeInfo GetImagePartType(string extension) { return extension switch { ".jpg" or ".jpeg" => ImagePartType.Jpeg, ".png" => ImagePartType.Png, ".gif" => ImagePartType.Gif, ".bmp" => ImagePartType.Bmp, ".tiff" => ImagePartType.Tiff, _ => ImagePartType.Jpeg }; } } }
由于WordHelper中使用的實(shí)體類是動(dòng)態(tài)的,替換的標(biāo)簽也在是實(shí)體類中根據(jù)特性指定的,特性類中指定標(biāo)簽名稱、是否圖片、圖片寬度、圖片高度等參數(shù),替換模板數(shù)據(jù)時(shí)會(huì)根據(jù)實(shí)體類反射解析實(shí)體中的特性來替換數(shù)據(jù)內(nèi)容,特性類代碼如下:
1 namespace Core.Attributes 2 { 3 [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = false)] 4 public class ExportTemplateAttribute : Attribute 5 { 6 public ExportTemplateAttribute(string templateName, bool isImage = false, double imageWidth = 0, double imageHeight = 0) 7 { 8 TemplateName = templateName; 9 IsImage = isImage; 10 ImageWidth = imageWidth; 11 ImageHeight = imageHeight; 12 } 13 /// <summary> 14 /// 是否圖片屬性 15 /// </summary> 16 public bool IsImage { get; set; } = false; 17 /// <summary> 18 /// 圖片寬度(單位:cm) 19 /// </summary> 20 public double ImageWidth { get; set; } = 0; 21 /// <summary> 22 /// 圖片高度(單位:cm) 23 /// </summary> 24 public double ImageHeight { get; set; } = 0; 25 /// 模板名稱 26 /// </summary> 27 public string TemplateName { get; set; } = string.Empty; 28 } 29 30 }
五、條形碼生成類,根據(jù)指定內(nèi)容、尺寸生成條碼,代碼如下:
using BarcodeStandard; using SkiaSharp; public class BarcodeUtils { public static MemoryStream GenerateBarcode(string text, BarcodeStandard.Type type, int width = 320, int height = 80) { var barcode = new Barcode { IncludeLabel = false, Alignment = BarcodeStandard.AlignmentPositions.Center, Width = width, Height = height, EncodedType = type, BackColor = SKColors.White, ForeColor = SKColors.Black, }; var image = barcode.Encode(type, text); // 創(chuàng)建內(nèi)存流 var encodedData = image.Encode(SKEncodedImageFormat.Png, 100); var stream = new MemoryStream(); encodedData.SaveTo(stream); stream.Position = 0; // 重置流位置以便讀取 return stream; } }
六、實(shí)體數(shù)據(jù)類,數(shù)據(jù)類中需要指定標(biāo)簽、以及是否圖片替換等屬性,示例代碼如下
using Core.Attributes; /// <summary> /// 導(dǎo)出模板 /// </summary> public class DateTemplate { /// <summary> /// 名稱 /// </summary> [ExportTemplate("name", false, 0, 0)] public string Name { get; set; } /// <summary> /// 編號(hào) /// </summary> [ExportTemplate("number", false, 0, 0)] public string Number { get; set; } /// <summary> /// 采樣時(shí)間 /// </summary> [ExportTemplate("specimenTime")] public DateTime CreateTime { get; set; } /// <summary> /// 接收時(shí)間 ///</summary> [ExportTemplate("receiveTime")] public DateTime? TestTime { get; set; } /// <summary> /// 報(bào)告時(shí)間 ///</summary> [ExportTemplate("reportTime")] public DateTime? ResultAuditTime { get; set; } /// <summary> /// 機(jī)構(gòu)名稱 /// </summary> [ExportTemplate("submissionUnit")] public string InstitutionName { get; set; } /// <summary> /// 性別 /// </summary> public EnumGender? Gender { get; set; } /// <summary> /// 年齡 /// </summary> public int Age { get; set; } /// <summary> /// 年齡單位 /// </summary> public EnumAgeUnit AgeUnit { get; set; } = EnumAgeUnit.FullYear; [ExportTemplate("ageStr")] public string AgeStr { get { return $"{Age}{AgeUnit.GetDescription()}"; } } [ExportTemplate("gender")] public string SexStr { get { return Gender.HasValue ? Gender.Value.GetDescription() : ""; } } /// 樣本類型 /// </summary> [ExportTemplate("specimenType")] public string SpecimenType { get; set; } /// <summary> /// 科室/部門 /// </summary> [ExportTemplate("department")] public string Department { get; set; } /// <summary> /// 標(biāo)本條碼 /// </summary> [ExportTemplate("Barcode", true, 4.2, 0.8)] public Stream? Barcode { get; set; } = null; /// <summary> /// 標(biāo)本外觀 /// </summary> [ExportTemplate("appearance")] public string SpecimenAppearance { get; set; } /// <summary> /// 送檢醫(yī)生 /// </summary> [ExportTemplate("doctor")] public string Doctor { get; set; } /// <summary> /// 床號(hào) /// </summary> [ExportTemplate("bedNum")] public string BedNum { get; set; } /// <summary> /// 是否鏡檢 /// </summary> public bool? IsMicroExam { get; set; } #endregion /// <summary> /// 建議與解釋 /// </summary> [ExportTemplate("notice")] public string Notice { get; set; } = string.Empty; /// <summary> /// 檢驗(yàn)員 /// </summary> [ExportTemplate("testor")] public string Testor { get; set; } = string.Empty; /// <summary> /// 檢驗(yàn)員 /// </summary> [ExportTemplate("auditor")] public string Auditor { get; set; } = string.Empty; /// <summary> /// 檢驗(yàn)員 /// </summary> [ExportTemplate("signers")] public string Signers { get; set; } = string.Empty; /// <summary> /// 臨床診斷 /// </summary> [ExportTemplate("clinicalDiag")] public string ClinicalDiagnosis { get; set; } = string.Empty; /// <summary> /// 手機(jī)號(hào)碼 /// </summary> [ExportTemplate("telphone")] public string Telphone { get; set; } = string.Empty; /// <summary> /// 門診/住院號(hào) /// </summary> [ExportTemplate("admisnum")] public string AdmissionNumber { get; set; } = string.Empty; }
列表的實(shí)體數(shù)據(jù)類和標(biāo)簽數(shù)據(jù)類是一樣的,此處不再舉例
七、生成文件,示例代碼如下:
1 //其中DataTemplate是標(biāo)簽數(shù)據(jù)類,ListItem1是列表1的實(shí)體類型,ListItem2 是列表2的實(shí)體類型 1和2 表示表格的索引值 2 var outdata = WordHelper.GenerateDocumentFromTemplate<DataTempalte, ListItem1, ListItem2>(templatePath, exportdata, resultItems, 1, resultITems1, 2);
八、文件導(dǎo)出,示例代碼如下:
/// <summary> /// 導(dǎo)出模板 /// </summary> /// <param name="input"></param> /// <returns></returns> [HttpPost("ExportResult")] [Description("導(dǎo)出結(jié)果")] public async Task<dynamic> ExportResult([FromBody] inputdata input) { var fileName="wordfile.docx"; /// 根據(jù)Id獲取數(shù)據(jù) var exportdata= serivce.getData(input.id) var outdata = WordHelper.GenerateDocumentFromTemplate<DataTempalte, ListItem1, ListItem2>(templatePath, exportdata, exportdata.list1, 1, exportdata.list2, 2); ///返回文件流 return new FileStreamResult(new MemoryStream(byteArray), "application/octet-stream") { FileDownloadName = fileName }; }
以上就是所有的代碼,感謝deepseek提供的解決方案,已調(diào)試通過,在開發(fā)過程中,遇到了以下問題
1、通過模板無法找到Text元素和Table元素,嘗試了很多方法都沒有解決,最后找到了原因,竟然是由于Text和Table引用的命名空間不正確,我們需要使用的命名空間是DocumentFormat.OpenXml.Wordprocessing,但是我在項(xiàng)目中使用的是DocumentFormat.OpenXml.Drawing,代碼編譯不會(huì)報(bào)錯(cuò),就是找不到指定元素
2、插入圖片元素不顯示,推測原因,是因?yàn)闃?biāo)簽中包含了其它元素,刪除即可
浙公網(wǎng)安備 33010602011771號(hào)