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)

功能模块

  1. 客户信息管理: 输入客户名称和地址
  2. 发票项目管理: 动态添加/删除产品项目
  3. 打印控制: 预览、打印、保存PDF
  4. 备注信息: 添加发票备注

核心方法

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特性

  1. 响应式设计: 适配屏幕和打印
  2. 打印优化: 移除不必要的边距和背景
  3. 专业样式: 公司颜色、表格样式、总计高亮
  4. 中文字体: 使用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

依赖包说明

  1. PuppeteerSharp (11.0.0): HTML转PDF的核心库
  2. 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)
{
    // 使用选择的打印机
}

性能优化建议

  1. 浏览器复用: 考虑复用Browser实例
  2. 模板缓存: 缓存生成的HTML模板
  3. 批量处理: 支持批量打印多个发票
  4. 进度显示: 添加进度条显示转换进度

安全注意事项

  1. 输入验证: 对用户输入进行验证
  2. 文件权限: 限制临时文件访问权限
  3. 资源清理: 确保及时释放浏览器资源
  4. 异常处理: 防止敏感信息泄露

版本历史

v1.0.0 (初始版本)

  • 基础HTML打印功能
  • 支持预览、打印、保存PDF
  • 动态数据绑定
  • 响应式HTML模板

文档生成时间: 2024年 项目版本: 1.0.0 最后更新: 2024年