html 转 pdf:基于 puppeteer 将 html 转 pdf

159 阅读2分钟

官网地址: Puppeteer

nodejs 中的代码实现:

import express from 'express';
import puppeteer from 'puppeteer';

const router = express.Router();

router.get('/out/printPDF', async (req, res) => {
    try{
        const browser = await puppeteer.launch({
            // 是否开启无头浏览器
            // headless 设置为 false 时会自动打开浏览器,可以方便调试
            headless: true
        });
        const page = await browser.newPage();

        // 设置浏览器宽高
        await page.setViewport({
            width: 1280, 
            height: 1080,
        })
        
        await page.goto('这里放需要转为 pdf 的 html 页面URL', { waitUntil: ['load', 'networkidle0'] });
        
        // 如果页面中需要加载 sessionStore 或 localStore 中的数据,可以先实现下列代码向 store 中存入数据再刷新页面
        // 向 localStore 中添加数据
        await page.evaluate(() => {
            // 如果有需要可以先清空 store 数据
            // localStorage.clear()
            localStorage.setItem('REMEMBER_USER_ID', '123456879')
        })

        // 重新刷新页面以加载 store 中的数据
        await page.reload({ waitUntil: ['load', 'networkidle0'] })

        // 下列代码用于等待某个特定的元素渲染完毕,可以确保某个元素加载完成后再打印页面
        // await page.waitForSelector('.userID')
        
        // 将页面转为 pdf
        const pdfBuffer = await page.pdf({
            path: '给 PDF 文件命名.pdf',
            format: 'A4', // pdf 纸张模式 A4、A5 等等
            printBackground: true, // 是否打印背景颜色
            // 如果这个属性为 true, 就意味着 puppeteer 在转换 pdf 时优先使用通过 @page 设置的样式。
            preferCSSPageSize: true 
        });
    
        // 关闭浏览器
        await browser.close();
    
        res.send(pdfBuffer)
    } catch(e) {
        res.send(`导出失败: ${e}`)
    }
});

export default router;

前端测试页面:

如果需要某一部分内容(例如 表格)一直保持在同一页不被切割隔开,可以通过设置该内容的css 为 break-inside: avoid; 来实现,如下代码所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test-general-pdf</title>

    <style>
        .container {
            width: 100vw;
            background: #000;

            display: flex;
            align-items: center;
            justify-content: center;
            flex-direction: column;
            color: #FFF;
            box-sizing: border-box;
        }

        .header {
            width: 100%;
            height: 10rem;
        }

        .context {
            width: 100%;
            flex: 1;
        }
        
        .footer {
            width: 100%;
            height: 10rem;
        }

        @media screen and (max-width: 1280px) {
            .container {
                background-color: red !important;
            }
        }

        /* 转化为 pdf 的时候会优先使用这里设置的样式 */ 
        @media print {
            .container {
                background-color: blue !important;
            }

            .not-break {
                /* 避免内容被切割开 */ 
                break-inside: avoid;
            }
        }
    </style>
</head>
<body>
    <div class="container"> 
        <header class="header">
            <h1>123456</h1>
        </header>
        <div class="context">
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <p>文字文字文字文字</p>
            <div class="not-break">
                <p>这里不分页</p>
                <p>这里不分页</p>
                <p>这里不分页</p>
                <p>这里不分页</p>
                <p>这里不分页</p>
                <p>这里不分页</p>
                <p>这里不分页</p>
                <p>这里不分页</p>
                <p>这里不分页</p>
                <p>这里不分页</p>
                <p>这里不分页</p>
                <p>这里不分页</p>
                <p>这里不分页</p>
                <p>这里不分页</p>
                <p>这里不分页</p>
                <p>这里不分页</p>
            </div>
        </div>
        <div class="footer">
            <p>底部</p>
        </div>
    </div>
</body>
</html>