一、前言
最近项目开发中,有一个根据word模板和指定的数据导出word文件的需求,word模板文件如下,需要将指定标签替换为数据中指定的字段,表格根据第一行的标签生成列表数据,将指定的标签替换为图片,word模板如下
二、开发准备,为了做成一个通用的word导出功能,只需要提供模板以及数据实体就可以实现导出不一样的数据,项目中主要使用以下Nuget包
1、DocumentFormat.OpenXml 用于word文件修改编辑操作
2、BarcodeLib 用于生成条码图片
3、SkiaSharp 用于生成跨平台的图片格式
三、word模板文件编辑,word模板文件图片如下,请注意:标签中不要换行加tab符等,否则读取模板文件时标签一个标签可能会变成多个标签

四、word生成帮助类,该类主要功能,根据指定的数据和模板生成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 根据模板生成文件合集 /// <summary> /// 根据指定实体类和模板生成Word文档,替换模板中的标签和图片 /// </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); // 打开文档进行修改 using(WordprocessingDocument doc = WordprocessingDocument.Open(stream, true)) { // 生成替换字典 Dictionary<string, string> replacements = GenerateReplacements(data); // 替换主文档内容 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; // 重置流位置以便后续读取 return stream; } } /// <summary> /// 根据模板文件(路径)根据实体配置替换标签,图片以及列表数据(1个列表) /// </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); // 打开文档进行修改 using(WordprocessingDocument doc = WordprocessingDocument.Open(stream, true)) { // 生成替换字典 Dictionary<string, string> replacements = GenerateReplacements(data); // 调用重载方法 // 替换主文档内容 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; // 重置流位置以便后续读取 return stream; } } /// <summary> /// 根据模板文件(路径)根据实体配置替换标签,图片以及列表数据(2个列表) /// </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. 从服务器读取模板文件 using var fileStream = new FileStream(FileUtils.GetMapPath(templatePath), FileMode.Open, FileAccess.Read); // 2. 创建内存流并复制模板内容 using var memoryStream = new MemoryStream(); fileStream.CopyTo(memoryStream); // 3. 使用OpenXML处理文档 memoryStream.Position = 0; // 重置流位置; // 打开文档进行修改 using(WordprocessingDocument doc = WordprocessingDocument.Open(memoryStream, true)) { // 生成替换字典 Dictionary<string, string> replacements = GenerateReplacements(data); // 调用重载方法 // 替换主文档内容 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> /// 根据模板文件(路径)根据实体配置替换标签,图片 /// </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); // 打开文档进行修改 using(WordprocessingDocument doc = WordprocessingDocument.Open(outputPath, true)) { // 生成替换字典 Dictionary<string, string> replacements = GenerateReplacements(data); // 替换主文档内容 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> /// 根据模板文件(路径)根据实体配置替换标签,图片以及列表数据(1个列表) /// </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); // 打开文档进行修改 using(WordprocessingDocument doc = WordprocessingDocument.Open(outputPath, true)) { // 生成替换字典 Dictionary<string, string> replacements = GenerateReplacements(data); // 调用重载方法 // 替换主文档内容 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> /// 根据模板文件(路径)根据实体配置替换标签,图片以及列表数据(2个列表) /// </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); // 打开文档进行修改 using(WordprocessingDocument doc = WordprocessingDocument.Open(outputPath, true)) { // 生成替换字典 Dictionary<string, string> replacements = GenerateReplacements(data); // 调用重载方法 // 替换主文档内容 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/默认图片.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文档从模板文件,仅替换标签 /// </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) { // 复制模板到新位置 File.Copy(templatePath, outputPath, overwrite: true); // 打开文档进行修改 using(WordprocessingDocument doc = WordprocessingDocument.Open(outputPath, true)) { // 替换主文档内容 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结构 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文档中动态添加表格行。 /// </summary> /// <param name="mainPart"></param> /// <param name="data">数据列表</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; // 如果没有替换项,则跳过 } // 替换行内占位符 ReplaceTextInRow(newRow, dict); table.AppendChild(newRow); } } /// <summary> /// 根据实体生成替换字典 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="data"></param> /// <returns></returns> private static Dictionary<string, string> GenerateReplacements<T>(T data) { // 假设data是一个包含属性的对象 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> /// 根据实体生成图片替换字典 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="data"></param> /// <returns></returns> private static Dictionary<string, Tuple<string, Stream, double, double>> GenerateReplacementImages<T>(T data) { // 假设data是一个包含属性的对象 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++)//循环自定义属性 { 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> /// 点位文本替换为图片 /// </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. 准备图片部件 PartTypeInfo imagePartType = GetImagePartType(imageName.GetExt()); ImagePart imagePart = mainPart.AddImagePart(imagePartType); imagePart.FeedData(imageStream); string imageRelId = mainPart.GetIdOfPart(imagePart); // 3. 计算图片尺寸(转换为EMU单位) const long emuPerCm = 360000; // 1 cm = 360,000 EMUs long widthEmu = (long)(widthCm * emuPerCm); long heightEmu = (long)(heightCm * emuPerCm); // 4. 创建图片元素 Drawing drawing = CreateImageElement(imageRelId, widthEmu, heightEmu, System.IO.Path.GetFileName(imageName)); foreach(var run in runsWithPlaceholder) { // 5. 移除原有文本 run.RemoveAllChildren<Text>(); // 6. 创建新的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> /// 创建图片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> /// 根据文件扩展名获取图片类型 /// </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中使用的实体类是动态的,替换的标签也在是实体类中根据特性指定的,特性类中指定标签名称、是否图片、图片宽度、图片高度等参数,替换模板数据时会根据实体类反射解析实体中的特性来替换数据内容,特性类代码如下:
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 }
五、条形码生成类,根据指定内容、尺寸生成条码,代码如下:
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); // 创建内存流 var encodedData = image.Encode(SKEncodedImageFormat.Png, 100); var stream = new MemoryStream(); encodedData.SaveTo(stream); stream.Position = 0; // 重置流位置以便读取 return stream; } }
六、实体数据类,数据类中需要指定标签、以及是否图片替换等属性,示例代码如下
using Core.Attributes; /// <summary> /// 导出模板 /// </summary> public class DateTemplate { /// <summary> /// 名称 /// </summary> [ExportTemplate("name", false, 0, 0)] public string Name { get; set; } /// <summary> /// 编号 /// </summary> [ExportTemplate("number", false, 0, 0)] public string Number { get; set; } /// <summary> /// 采样时间 /// </summary> [ExportTemplate("specimenTime")] public DateTime CreateTime { get; set; } /// <summary> /// 接收时间 ///</summary> [ExportTemplate("receiveTime")] public DateTime? TestTime { get; set; } /// <summary> /// 报告时间 ///</summary> [ExportTemplate("reportTime")] public DateTime? ResultAuditTime { get; set; } /// <summary> /// 机构名称 /// </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> /// 标本条码 /// </summary> [ExportTemplate("Barcode", true, 4.2, 0.8)] public Stream? Barcode { get; set; } = null; /// <summary> /// 标本外观 /// </summary> [ExportTemplate("appearance")] public string SpecimenAppearance { get; set; } /// <summary> /// 送检医生 /// </summary> [ExportTemplate("doctor")] public string Doctor { get; set; } /// <summary> /// 床号 /// </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> /// 检验员 /// </summary> [ExportTemplate("testor")] public string Testor { get; set; } = string.Empty; /// <summary> /// 检验员 /// </summary> [ExportTemplate("auditor")] public string Auditor { get; set; } = string.Empty; /// <summary> /// 检验员 /// </summary> [ExportTemplate("signers")] public string Signers { get; set; } = string.Empty; /// <summary> /// 临床诊断 /// </summary> [ExportTemplate("clinicalDiag")] public string ClinicalDiagnosis { get; set; } = string.Empty; /// <summary> /// 手机号码 /// </summary> [ExportTemplate("telphone")] public string Telphone { get; set; } = string.Empty; /// <summary> /// 门诊/住院号 /// </summary> [ExportTemplate("admisnum")] public string AdmissionNumber { get; set; } = string.Empty; }
列表的实体数据类和标签数据类是一样的,此处不再举例
七、生成文件,示例代码如下:
1 //其中DataTemplate是标签数据类,ListItem1是列表1的实体类型,ListItem2 是列表2的实体类型 1和2 表示表格的索引值 2 var outdata = WordHelper.GenerateDocumentFromTemplate<DataTempalte, ListItem1, ListItem2>(templatePath, exportdata, resultItems, 1, resultITems1, 2);
八、文件导出,示例代码如下:
/// <summary> /// 导出模板 /// </summary> /// <param name="input"></param> /// <returns></returns> [HttpPost("ExportResult")] [Description("导出结果")] public async Task<dynamic> ExportResult([FromBody] inputdata input) { var fileName="wordfile.docx"; /// 根据Id获取数据 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提供的解决方案,已调试通过,在开发过程中,遇到了以下问题
1、通过模板无法找到Text元素和Table元素,尝试了很多方法都没有解决,最后找到了原因,竟然是由于Text和Table引用的命名空间不正确,我们需要使用的命名空间是DocumentFormat.OpenXml.Wordprocessing,但是我在项目中使用的是DocumentFormat.OpenXml.Drawing,代码编译不会报错,就是找不到指定元素
2、插入图片元素不显示,推测原因,是因为标签中包含了其它元素,删除即可
浙公网安备 33010602011771号