根据Word模板导出word文档,包含文本标签替换为图片,生成列表数据,以及标签替换等功能
一、前言
最近项目开发中,有一个根据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、插入图片元素不显示,推测原因,是因为标签中包含了其它元素,删除即可
根据Word模板导出word文档,包含文本标签替换为图片,生成列表数据,以及标签替换等功能的更多相关文章
- Aspose.Words利用Word模板导出Word文档
今天工作中遇到了导出Word文档的问题,但是在搜索Aspose.Words 导出Word文档时发现网上的方法都是有头没尾的,有的只有一小段实例,让人看着摸不着头脑.借着https://www.cnbl ...
- 利用模板导出文件(二)之jacob利用word模板导出word文件(Java2word)
https://blog.csdn.net/Fishroad/article/details/47951061?locationNum=2&fps=1 先下载jacob.jar包.解压后将ja ...
- C#实现按Word模板导出Word(加书签bookMark)
本方法是针对word导出操作,需要制作好的模板文件 模板.doc 引入应用Microsoft.Office.Interop.Word 11.0 (office2003) 导出文件注意:有时候迅雷会在 ...
- .net core 使用NPOI填充Word模板导出Word
最近工作用到在Word模板插入数据库数据,导出一个带数据的Word文件,想起来之前操作Word都是用微软提供的Microsoft.Office.Interop.Word,而在最新的..NET CORE ...
- java根据word模板导出word文件
1.word模板文件处理,如下图所示在word 文档中填值的地方写入占位变量 2.将word文档另存为xml文件.编辑如下图,找到填写的占位,修改为${bcrxm}格式 3.将文件后缀名改为.ftl文 ...
- spring boot:swagger3文档展示分页和分栏的列表数据(swagger 3.0.0 / spring boot 2.3.3)
一,什么情况下需要展示分页和分栏的数据的文档? 分页时,页面上展示的是同一类型的列表的数据,如图: 分栏时,每行都是一个列表,而且展示的数据类型也可能不同 这也是两种常用的数据返回形式 说明:刘宏缔的 ...
- SpringBoot集成文件 - 如何基于POI-tl和word模板导出庞大的Word文件?
前文我们介绍了通过Apache POI通过来导出word的例子:那如果是word模板方式,有没有开源库通过模板方式导出word呢?poi-tl是一个基于Apache POI的Word模板引擎,也是一个 ...
- JSP利用freemarker生成基于word模板的word文档
利用freemarker生成基于word模板的word文档 freemarker简介 FreeMarker是一个用Java语言编写的模板引擎,它基于模板来生成文本输出.FreeMarker与Web容器 ...
- C#通过模板导出Word(文字,表格,图片)
C#通过模板导出Word(文字,表格,图片) C#导出Word,Excel的方法有很多,这次因为公司的业务需求,需要导出内容丰富(文字,表格,图片)的报告,以前的方法不好使,所以寻找新的导出方法, ...
- word模板导出的几种方式:第二种:C#通过模板导出Word(文字,表格,图片) 占位符替换
原文出处:https://www.cnblogs.com/ilefei/p/3508463.html 一:模板的创建 (注意文件后缀只能是.docx或.doct) 在需要位置 插入-文档部件-域, ...
随机推荐
- 国际化利器 Intl Messageformat
我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品.我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值. 本文作者:霜序 Formats ICU Message string ...
- Web前端入门第 50 问:CSS 内容溢出怎么处理?
溢出:盒模型装不下内容的时候,超出盒子大小的内容就称之为内容溢出,这里的内容又分为盒模型和文本,所以 CSS 在处理溢出时候也分为文本和盒模型两种情况. 正常情况内容溢出应该换行自动撑开盒子大小,但某 ...
- codeup之字符串逆序存放
Description 写一个函数将一个字符串按反序存放.在主函数中输入一个字符串,通过调用该函数,得到该字符串按反序存放后的字符串,并输出. Input 一行字符串. Output 输入字符串反序存 ...
- Spring异常处理 bug !!!同一份代码,结果却不一样?
1. 背景 在上周遇到一个spring bug的问题,将其记录一下.简化的代码如下: public void insert() { try { Person person = new Person() ...
- 第1讲、#PyTorch教学环境搭建与Tensor基础操作详解
引言 PyTorch是当前深度学习领域最流行的框架之一,因其动态计算图和直观的API而备受开发者青睐.本文将从零开始介绍PyTorch的环境搭建与基础操作,适合各种平台的用户和深度学习初学者. 1. ...
- 面试题:JAVA中final关键字的作用
final关键字的功能概述 在Java中,关键字 final 的意思是终态,可以用于声明变量.方法和类,分别表示基本类型变量值不可变,引用类型变量引用地址不可变但值可变,方法不可被覆盖,类不可被继 ...
- tkela二次开发之lay文件解析
在tekla的一些配置文件中绝大都是本文格式存储的,如.dim:.ad:.vi;.tpl等文件:但是其中.lay文件却是一个例外:这个文件用txt开打时里面是会有乱码的. 我们知道这个文件是在软件的界 ...
- [原创]《C#高级GDI+实战:从零开发一个流程图》第03章:画一个线,连接两个矩形!
一.前言 上一节我们实现了多个不同颜色的可拖动的矩形,那么这一节就来看一下如何将这些矩形连起来吧. 相信看完的你,一定会有所收获! 本文地址:https://www.cnblogs.com/lesli ...
- hot100之数组
最大子数组和(053) 先看代码 class Solution { public int maxSubArray(int[] nums) { int n = nums.length; int subS ...
- Eplan教程:主数据创建
从今天开始,Eplan团队将带着大家一起,从项目早期的规划报价设计开始,经历原理图设计.PLC设计.盘柜三维设计.线束三维设计,直至输出供生产所需的各类报表. 该系列共分为几个部分 1.设计主数据-部 ...