ThinkPHP项目PDF生成与打印

wen PHP项目 3

ThinkPHP项目PDF生成与打印全攻略:从基础集成到高效实践


目录导读

  1. 为什么在ThinkPHP中需要PDF生成与打印?
  2. 技术选型:主流PDF库对比与选型建议(Dompdf/TcPDF/Mpdf)
  3. 环境配置:在ThinkPHP 6/8中安装与初始化PDF库
  4. 核心实战:动态渲染HTML模板并生成PDF(含代码示例)
  5. 进阶技巧:处理中文、CSS样式及分页符的坑与解决方案
  6. 打印优化:如何实现浏览器直接打印(而非下载)?
  7. 性能与安全:大文件处理、缓存策略及防注入
  8. 常见问题解答(FAQ)

为什么在ThinkPHP项目中需要PDF生成与打印?

在电商(订单/发票)、教育(成绩单)、OA系统(合同/报表)等场景中,PDF的“跨平台固定排版”和“不可篡改性”是HTML页面无法替代的,对于使用ThinkPHP框架的开发者而言,将数据库查询结果动态渲染为PDF,并支持打印,是完成业务闭环的最后一公里。

ThinkPHP项目PDF生成与打印

技术选型:主流PDF库对比与选型建议

搜索引擎中关于ThinkPHP生成PDF的讨论,90%集中在以下三个库:

  • Dompdf:对CSS 2.1支持良好,上手最快,适合简单报表,但处理复杂CSS(如Flex布局)时会“翻车”。
  • Mpdf:内置强大的Unicode字体处理,对中文(尤其生僻字)、以及复杂表格支持极佳,性能比Dompdf慢,但胜在稳定。
  • TcPDF:老牌库,运行快,但编码处理繁琐,对HTML转义要求高。

选型结论(基于SEO高权重内容综合):如果项目以中文界面为主,且需要兼容嵌套表格,强烈推荐Mpdf;如果是简单协议展示,Dompdf足够,本篇文章以下实战代码均基于Mpdf进行讲解。

环境配置:在ThinkPHP 6/8中安装与初始化PDF库

假设您已通过Composer创建ThinkPHP项目,安装Mpdf只需一行命令:

composer require mpdf/mpdf

在ThinkPHP中,我们习惯将PDF服务封装成公共服务类,在 app/common/service/ 目录下创建 PdfService.php

namespace app\common\service;
use Mpdf\Mpdf;
class PdfService
{
    protected $mpdf;
    public function __construct(array $config = [])
    {
        $this->mpdf = new Mpdf([
            'mode' => 'utf-8', // 必须指定UTF-8
            'format' => 'A4',
            'default_font_size' => 12,
            'default_font' => 'simsun', // 设置默认中文字体(需提前安装)
            'tempDir' => runtime_path() . 'pdf_tmp', // 设置临时目录避免权限问题
        ]);
    }
}

注意:如果您使用Dompdf,需要手动引入字体文件;而Mpdf支持通过 fontDir 配置外部字体目录。

核心实战:动态渲染HTML模板并生成PDF

业务场景:生成用户订单详情,我们不会直接在控制器中写HTML,而是使用ThinkPHP模板引擎渲染,再导出为PDF。

第一步:创建视图文件 application/index/view/order/pdf.html重点: 为了让PDF输出正常,必须使用内联样式,尽量不要依赖外链CSS。

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body>
<div style="text-align:center;border-bottom:1px solid #ccc;">
  <h2>订单号:{$order.order_no}</h2>
</div>
<table style="width:100%;border-collapse:collapse;">
  <tr>
    <td style="padding:5px;border:1px solid #aaa;">商品名称</td>
    <td style="padding:5px;border:1px solid #aaa;">价格</td>
  </tr>
  {volist name="goods" id="vo"}
  <tr>
    <td style="border:1px solid #aaa;">{$vo.goods_name}</td>
    <td style="border:1px solid #aaa;">{$vo.price}</td>
  </tr>
  {/volist}
</table>
</body>
</html>

第二步:在控制器中调用渲染与输出

use app\common\service\PdfService;
use think\facade\View;
public function generatePdf()
{
    // 模拟数据
    $order = ['order_no' => '20250001'];
    $goods = [['goods_name' => 'PHP编程入门', 'price' => 59], ['goods_name' => 'ThinkPHP6实战', 'price' => 79]];
    // 1. 渲染HTML字符串
    $html = View::fetch('order/pdf', ['order' => $order, 'goods' => $goods]);
    // 2. 生成PDF
    $pdfService = new PdfService();
    $pdfService->mpdf->WriteHTML($html);
    // 3. 输出到浏览器(内联预览)
    $pdfService->mpdf->Output('order.pdf', \Mpdf\Output\Destination::INLINE);
}

这一整套流程是搜索引擎中最高赞的实践方式。

进阶技巧:处理中文、CSS样式及分页符的坑与解决方案

  • 中文乱码:这是必填坑,除了 mode => 'utf-8' 外,必须指定支持中文的字体,在Mpdf中,最简单的方式是下载 simsun.ttc(宋体)放入 public/fonts/ 目录,然后在构造函数中添加:
    'fontDir' => public_path() . 'fonts', 
    'fontdata' => ['simsun' => ['R' => 'simsun.ttc']]
  • CSS兼容性:Mpdf不支持 position: fixed 作为页眉页脚,应使用 <htmlheader> 标签或 SetHeader() 方法,避免使用 float 布局,尽量使用 table 布局。
  • 分页控制:在渲染长表格时,防止行被截断,在 <tr> 上添加 style="page-break-inside: avoid;" 是关键。

打印优化:如何实现浏览器直接打印(而非下载)?

需求不同,方案也不同:

  • 场景A:直接打印生成的PDF:使用上述代码中的 INLINE 模式,浏览器会打开PDF预览窗口,用户需再点击Adobe阅读器的打印按钮,体验一般。
  • 场景B:生成HTML,然后调用window.print():在ThinkPHP中,我们可以直接输出HTML页面,并在按钮点击时触发JavaScript的打印,这种方式通常比打印PDF更受用户欢迎。
    • 先访问 ?action=html 获取结果,然后在页面中引入:
      <button onclick="window.print()">直接打印</button>
    • CSS中定义 @media print { .no-print { display:none; } } 来隐藏“返回”按钮。

性能与安全:大文件处理、缓存策略及防注入

  • 性能:当生成上千条记录的文件时,不要一次性渲染整个HTML数组,采用 for 循环分块写入Mpdf:
    foreach ($chunk_array as $chunk_data) {
        $html = View::fetch('order/pdf_item', ['items' => $chunk_data]);
        $pdfService->mpdf->WriteHTML($html);
    }
  • 安全:用户输入的内容(如商品名称)一定要经过 htmlspecialchars() 转义后再插入HTML模板,否则恶意脚本会导致PDF解析错误或信息泄露。
  • 缓存:如果订单内容不经常变动,可以结合ThinkPHP的缓存把生成的PDF字节存入Redis,减少多次查询数据库的压力。

常见问题解答(FAQ)

问:ThinkPHP6使用Mpdf时提示“Class 'Mpdf\Mpdf' not found”? 答:大概率是Composer自动加载未更新,在项目根目录执行 composer dump-autoload 后重启服务即可。

问:生成的PDF中,中文字体内文字是方框(□□□)怎么解决? 答:检查是否在Mpdf构造函数正确指定了 fontDirfontdata,并且在HTML中设置 font-family: simsun;,请务必不要使用浏览器默认字体名。

问:如何让PDF文件直接下载而不是预览? 答:将 Output() 参数改为 \Mpdf\Output\Destination::DOWNLOAD 即可。


(文章完)

抱歉,评论功能暂时关闭!