You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用go-wkhtmltopdf在Go中生成分页规整的PDF报表?

解决go-wkhtmltopdf生成PDF时页眉错位、表格跨页混乱的问题

核心思路

问题根源在于wkhtmltopdf的自动分页逻辑和自定义页眉/表格布局冲突,要强制每页内容(页眉+20行表格+页脚)作为独立块,禁止跨页拆分,同时禁用工具默认的页眉页脚,改用HTML内联的固定布局。

具体实现步骤

1. 编写带分页控制的HTML模板

重点用CSS控制分页和布局边界:

  • 给每页容器设置page-break-after: always(最后一页取消),确保每页结束后强制分页
  • 给表格容器设置page-break-inside: avoid,防止表格被拆分到两页
  • 预留页眉页脚的固定空间,避免内容重叠

示例HTML结构:

<!DOCTYPE html>
<html>
<head>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        /* 每页容器:强制分页,禁止内部拆分 */
        .page-container {
            page-break-after: always;
            page-break-inside: avoid;
            width: 100%;
            height: 100vh;
            position: relative;
        }
        /* 固定页眉:锁定在页顶 */
        .page-header {
            position: absolute;
            top: 20px;
            left: 20px;
            right: 20px;
            height: 60px;
            border-bottom: 1px solid #ccc;
        }
        .logo {
            height: 50px;
            width: auto;
        }
        /* 表格容器:预留页眉页脚空间 */
        .table-wrapper {
            padding: 100px 20px 80px;
        }
        table {
            width: 100%;
            border-collapse: collapse;
        }
        th, td {
            border: 1px solid #ccc;
            padding: 8px;
            text-align: left;
        }
        /* 固定页脚:锁定在页底 */
        .page-footer {
            position: absolute;
            bottom: 20px;
            left: 20px;
            right: 20px;
            height: 40px;
            border-top: 1px solid #ccc;
            text-align: center;
            font-size: 12px;
        }
        /* 最后一页取消强制分页 */
        .page-container:last-child {
            page-break-after: avoid;
        }
    </style>
</head>
<body>
    {{range .Pages}}
    <div class="page-container">
        <div class="page-header">
            <img src="{{.LogoPath}}" class="logo" alt="报表Logo">
            <h3>业务数据报表</h3>
        </div>
        <div class="table-wrapper">
            <table>
                <thead>
                    <tr>
                        <th>序号</th>
                        <th>业务名称</th>
                        <th>数值</th>
                    </tr>
                </thead>
                <tbody>
                    {{range .Rows}}
                    <tr>
                        <td>{{.ID}}</td>
                        <td>{{.Name}}</td>
                        <td>{{.Value}}</td>
                    </tr>
                    {{end}}
                </tbody>
            </table>
        </div>
        <div class="page-footer">
            第{{.PageNum}}页 / 共{{.TotalPages}}页
        </div>
    </div>
    {{end}}
</body>
</html>

2. Go后端处理:拆分数据为每页20行

在Go代码中提前将原始数据按每页20行拆分,生成分页数据结构后渲染模板,避免依赖CSS自动分页的不确定性:

package main

import (
	"html/template"
	"os"
	"github.com/SebastiaanKlippert/go-wkhtmltopdf"
)

// 分页数据结构
type PageData struct {
	LogoPath   string
	PageNum    int
	TotalPages int
	Rows       []RowData
}

type RowData struct {
	ID    int
	Name  string
	Value string
}

func main() {
	// 模拟原始业务数据
	allRows := make([]RowData, 123)
	for i := 0; i < 123; i++ {
		allRows[i] = RowData{
			ID:    i + 1,
			Name:  "业务项" + string(rune(i+1)),
			Value: "¥" + string(rune(i*100+50)),
		}
	}

	// 拆分数据为每页20行
	var pages []PageData
	pageSize := 20
	totalPages := (len(allRows) + pageSize - 1) / pageSize
	for i := 0; i < totalPages; i++ {
		start := i * pageSize
		end := start + pageSize
		if end > len(allRows) {
			end = len(allRows)
		}
		pages = append(pages, PageData{
			LogoPath:   "./assets/logo.png", // 替换为实际Logo路径
			PageNum:    i + 1,
			TotalPages: totalPages,
			Rows:       allRows[start:end],
		})
	}

	// 渲染HTML模板
	tmpl, err := template.ParseFiles("report.tmpl")
	if err != nil {
		panic(err)
	}
	htmlFile, err := os.Create("temp_report.html")
	if err != nil {
		panic(err)
	}
	defer htmlFile.Close()
	err = tmpl.Execute(htmlFile, map[string]interface{}{"Pages": pages})
	if err != nil {
		panic(err)
	}

	// 初始化PDF生成器
	pdfg, err := wkhtmltopdf.NewPDFGenerator()
	if err != nil {
		panic(err)
	}
	// 禁用默认页眉页脚,避免和自定义布局冲突
	pdfg.Dpi.Set(300)
	pdfg.PageSize.Set(wkhtmltopdf.PageSizeA4)
	pdfg.MarginTop.Set(0)
	pdfg.MarginBottom.Set(0)
	pdfg.MarginLeft.Set(0)
	pdfg.MarginRight.Set(0)

	// 添加渲染好的HTML页面
	page := pdfg.NewPage("file://" + htmlFile.Name())
	page.EnableLocalFileAccess.Set(true) // 允许加载本地Logo图片

	// 生成并保存PDF
	err = pdfg.Create()
	if err != nil {
		panic(err)
	}
	err = pdfg.WriteFile("business_report.pdf")
	if err != nil {
		panic(err)
	}
}

3. 关键注意事项

  • 必须禁用wkhtmltopdf的默认页眉页脚,否则会和HTML内的固定布局重叠错位
  • 后端提前拆分数据为每页20行,比依赖CSS控制行数更可靠,彻底避免跨页行
  • 开启EnableLocalFileAccess确保本地Logo图片能正常加载
  • 用page-break-inside: avoid强制每页容器作为整体,不会被拆分

内容的提问来源于stack exchange,提问作者Sunshine Gaze

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 17:09:53