HTML打印模板系统 - 技术文档
项目概述
一个基于C# .NET 8和Windows Forms的HTML打印模板系统,使用PuppeteerSharp将HTML模板转换为PDF,支持打印预览、直接打印和PDF保存功能。
系统架构
技术栈
- 开发框架: .NET 8.0
- UI框架: Windows Forms
- HTML转PDF: PuppeteerSharp
- PDF打印: 系统默认打印机
项目结构
PrintDemo/
├── PrintDemo.csproj # 项目配置文件
├── Program.cs # 应用程序入口点
├── Forms/
│ ├── MainForm.cs # 主窗体代码
│ └── MainForm.Designer.cs # 主窗体设计器代码
├── Models/
│ └── InvoiceData.cs # 数据模型类
└── Services/
└── PrintService.cs # 打印服务类
核心组件详解
1. 数据模型 (Models/InvoiceData.cs)
InvoiceData 类
/// <summary>
/// 发票数据模型
/// </summary>
public class InvoiceData
{
public string InvoiceNumber { get; set; } = string.Empty;
public DateTime InvoiceDate { get; set; }
public string CompanyName { get; set; } = string.Empty;
public string CompanyAddress { get; set; } = string.Empty;
public string CompanyPhone { get; set; } = string.Empty;
public string CustomerName { get; set; } = string.Empty;
public string CustomerAddress { get; set; } = string.Empty;
public List<InvoiceItem> Items { get; set; } = new();
public string Notes { get; set; } = string.Empty;
/// <summary>
/// 计算总金额
/// </summary>
public decimal TotalAmount => Items.Sum(item => item.TotalPrice);
}
InvoiceItem 类
/// <summary>
/// 发票项目模型
/// </summary>
public class InvoiceItem
{
public string ProductName { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
/// <summary>
/// 计算项目总价
/// </summary>
public decimal TotalPrice => Quantity * UnitPrice;
}
2. 打印服务 (Services/PrintService.cs)
核心方法
1. 预览发票
/// <summary>
/// 预览发票
/// </summary>
public async Task PreviewInvoiceAsync(InvoiceData invoiceData)
{
var pdfBytes = await GeneratePdfFromInvoiceAsync(invoiceData);
var tempPdfPath = Path.Combine(_tempDirectory, $"preview_{Guid.NewGuid()}.pdf");
await File.WriteAllBytesAsync(tempPdfPath, pdfBytes);
// 使用默认程序打开PDF进行预览
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = tempPdfPath,
UseShellExecute = true
});
}
2. 直接打印
/// <summary>
/// 打印发票
/// </summary>
public async Task PrintInvoiceAsync(InvoiceData invoiceData)
{
var pdfBytes = await GeneratePdfFromInvoiceAsync(invoiceData);
var tempPdfPath = Path.Combine(_tempDirectory, $"print_{Guid.NewGuid()}.pdf");
await File.WriteAllBytesAsync(tempPdfPath, pdfBytes);
// 打印PDF文件
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = tempPdfPath,
Verb = "print",
UseShellExecute = true
});
}
3. 生成HTML模板
/// <summary>
/// 生成发票HTML模板
/// </summary>
private string GenerateInvoiceHtml(InvoiceData data)
{
// 动态生成HTML内容
return $@"
<!DOCTYPE html>
<html>
<head>
<style>
/* CSS样式 */
</style>
</head>
<body>
<!-- HTML内容 -->
</body>
</html>";
}
3. 主窗体 (Forms/MainForm.cs)
功能模块
- 客户信息管理: 输入客户名称和地址
- 发票项目管理: 动态添加/删除产品项目
- 打印控制: 预览、打印、保存PDF
- 备注信息: 添加发票备注
核心方法
1. 创建发票数据
/// <summary>
/// 创建发票数据
/// </summary>
private InvoiceData CreateInvoiceData()
{
return new InvoiceData
{
InvoiceNumber = $"INV-{DateTime.Now:yyyyMMdd}-{new Random().Next(1000, 9999)}",
InvoiceDate = DateTime.Now,
CompanyName = "示例科技有限公司",
CompanyAddress = "北京市海淀区中关村大街1号",
CompanyPhone = "010-12345678",
CustomerName = txtCustomerName.Text,
CustomerAddress = txtCustomerAddress.Text,
Items = _invoiceItems.ToList(),
Notes = txtNotes.Text
};
}
2. 数据网格配置
/// <summary>
/// 配置数据网格视图
/// </summary>
private void ConfigureDataGridView()
{
dataGridViewItems.AutoGenerateColumns = false;
dataGridViewItems.Columns.Clear();
// 添加列定义
dataGridViewItems.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = "ProductName",
HeaderText = "产品名称",
Width = 150
});
// ... 其他列配置
}
HTML模板设计
模板结构
<!DOCTYPE html>
<html>
<head>
<style>
/* 打印优化样式 */
@media print {
/* 打印特定样式 */
}
/* 通用样式 */
.invoice-container { max-width: 800px; }
.items-table { width: 100%; }
.total-amount { color: #e74c3c; }
</style>
</head>
<body>
<div class="invoice-container">
<!-- 头部信息 -->
<div class="header">商业发票</div>
<!-- 发票信息 -->
<div class="invoice-info">
<div>发票号码: {InvoiceNumber}</div>
<div>客户名称: {CustomerName}</div>
</div>
<!-- 项目表格 -->
<table class="items-table">
<thead>...</thead>
<tbody>
<!-- 动态生成项目行 -->
</tbody>
</table>
<!-- 总计 -->
<div class="total-section">
总计: {TotalAmount:C2}
</div>
<!-- 签字区域 -->
<div class="signature-section">
<div>客户签字</div>
<div>公司盖章</div>
</div>
</div>
</body>
</html>
CSS特性
- 响应式设计: 适配屏幕和打印
- 打印优化: 移除不必要的边距和背景
- 专业样式: 公司颜色、表格样式、总计高亮
- 中文字体: 使用Microsoft YaHei确保中文显示
项目配置文件
PrintDemo.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PuppeteerSharp" Version="11.0.0" />
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
</ItemGroup>
</Project>
编译和运行指南
系统要求
- .NET 8.0 SDK或更高版本
- Windows操作系统
- 网络连接(首次运行需要下载Chromium)
- 至少1GB可用磁盘空间
编译步骤
# 1. 创建项目目录
mkdir PrintDemo
cd PrintDemo
# 2. 创建项目结构
mkdir Forms Models Services
# 3. 创建项目文件
# 将PrintDemo.csproj内容复制到文件中
# 4. 添加代码文件
# 按照项目结构创建对应的.cs文件
# 5. 编译项目
dotnet build
# 6. 运行程序
dotnet run
依赖包说明
- PuppeteerSharp (11.0.0): HTML转PDF的核心库
- System.Drawing.Common (8.0.0): Windows Forms图形支持
使用说明
1. 界面操作
- 客户信息: 在顶部输入客户名称和地址
- 发票项目:
- 使用"添加项目"按钮添加新产品
- 使用"删除项目"按钮删除选中产品
- 直接在网格中编辑产品信息
- 打印操作:
- 打印预览: 生成PDF并在默认查看器中打开
- 直接打印: 发送到默认打印机
- 保存PDF: 保存为本地PDF文件
2. 数据流
用户输入 → InvoiceData对象 → HTML模板 → PDF文件 → 打印/保存
3. 错误处理
- 网络错误: 浏览器下载失败提示
- 文件权限: 临时文件创建失败处理
- 打印错误: 打印机不可用提示
技术实现细节
1. 异步处理
所有打印操作都使用async/await模式,避免UI线程阻塞:
private async void btnPreview_Click(object sender, EventArgs e)
{
try
{
Cursor = Cursors.WaitCursor;
btnPreview.Enabled = false;
var invoiceData = CreateInvoiceData();
await _printService.PreviewInvoiceAsync(invoiceData);
}
catch (Exception ex)
{
MessageBox.Show($"预览失败: {ex.Message}", "错误");
}
finally
{
Cursor = Cursors.Default;
btnPreview.Enabled = true;
}
}
2. 临时文件管理
// 创建临时目录
_tempDirectory = Path.Combine(Path.GetTempPath(), "PrintDemo");
Directory.CreateDirectory(_tempDirectory);
// 生成唯一文件名
var tempPdfPath = Path.Combine(_tempDirectory, $"preview_{Guid.NewGuid()}.pdf");
3. 浏览器管理
private async Task EnsureBrowserInstalledAsync()
{
using var browserFetcher = new BrowserFetcher();
if (!browserFetcher.CanDownloadAsync(BrowserFetcher.DefaultRevision))
{
throw new InvalidOperationException("无法下载浏览器");
}
if (!browserFetcher.LocalRevisions().Any())
{
await browserFetcher.DownloadAsync(BrowserFetcher.DefaultRevision);
}
}
扩展和定制
1. 自定义模板
修改GenerateInvoiceHtml方法中的HTML和CSS:
private string GenerateInvoiceHtml(InvoiceData data)
{
// 修改此方法以使用不同的HTML模板
return CustomTemplateGenerator.Generate(data);
}
2. 添加新模板类型
创建新的模板生成器类:
public class CustomTemplateGenerator
{
public static string Generate(InvoiceData data)
{
// 自定义模板逻辑
}
}
3. 支持更多数据源
扩展InvoiceData类以支持更多字段:
public class ExtendedInvoiceData : InvoiceData
{
public string TaxNumber { get; set; }
public decimal TaxRate { get; set; }
public decimal TaxAmount => TotalAmount * TaxRate;
}
常见问题解答
Q1: 首次运行为什么很慢?
A: 首次运行需要下载Chromium浏览器(约200MB),后续运行会很快。
Q2: 如何更改默认公司信息?
A: 修改CreateInvoiceData方法中的硬编码值:
CompanyName = "您的公司名称",
CompanyAddress = "您的公司地址",
CompanyPhone = "您的公司电话"
Q3: 如何调整PDF页面大小?
A: 修改PdfOptions配置:
var pdfBytes = await page.PdfAsync(new PdfOptions
{
Format = PaperFormat.A4, // 改为PaperFormat.Letter等
PrintBackground = true,
MarginOptions = new MarginOptions
{
Top = "2cm", // 调整边距
Right = "2cm",
Bottom = "2cm",
Left = "2cm"
}
});
Q4: 如何支持更多打印机选项?
A: 使用系统打印对话框:
using var printDialog = new PrintDialog();
if (printDialog.ShowDialog() == DialogResult.OK)
{
// 使用选择的打印机
}
性能优化建议
- 浏览器复用: 考虑复用Browser实例
- 模板缓存: 缓存生成的HTML模板
- 批量处理: 支持批量打印多个发票
- 进度显示: 添加进度条显示转换进度
安全注意事项
- 输入验证: 对用户输入进行验证
- 文件权限: 限制临时文件访问权限
- 资源清理: 确保及时释放浏览器资源
- 异常处理: 防止敏感信息泄露
版本历史
v1.0.0 (初始版本)
- 基础HTML打印功能
- 支持预览、打印、保存PDF
- 动态数据绑定
- 响应式HTML模板
文档生成时间: 2024年 项目版本: 1.0.0 最后更新: 2024年