资讯动态

PHP实战:用mpdf实现订单报表导出PDF完整指南

发布时间:2026/9/8 7:49:36 来源:尧图企业网站定制
最近在做一个订单系统客户那边提了个需求表单提交之后后台要能直接把数据导成一份规范的PDF文件方便打印、留档、发给上下游。翻了一圈方案最后选了PHP生态里很成熟的mpdf库来落地。折腾了一轮下来把过程中的思路、代码、踩过的坑都整理出来给准备做类似功能的朋友一个参考。先说结论如果你也是PHP项目需要把HTML样式的报表转成PDF而且对中文字体、复杂表格、页眉页脚有要求mpdf基本是最省事的方案。它的核心思路就是“把HTML/CSS喂给它它还你一个排版好的PDF文件”不像TCPDF那样需要你手动控制坐标和分页开发效率完全不在一个量级。1. 内容整体设计与思路拆解1.1 为什么是mpdf而不是其他PDF库在选型阶段我对比过好几个PHP PDF生成方案TCPDF、FPDF、Dompdf、wkhtmltopdf加上mpdf每个都试了一圈。老实说TCPDF和FPDF是老前辈文档也多但它们的API风格偏底层画线、定位、逐行输出内容写起来特别像在“手搓”PDF。你要生成一个带表头、合并单元格、多页分页的报表代码量会大到你怀疑人生。Dompdf对CSS的支持不错但遇到复杂表格或中文字体时偶尔会有渲染错位的情况。wkhtmltopdf是另一条路子它其实是调系统里的浏览器内核去渲染网页所以效果最接近浏览器但它依赖外部二进制程序在服务器上部署、维护都比较折腾。mpdf的优势在于它本身就把“HTML转PDF”这个事做得非常彻底。它对CSS 2.1的支持比较完整支持内联样式、style标签、外部CSS文件通过file_get_contents引入而且内置了分页控制、页眉页脚、水印、目录生成这些实用功能。最关键的是mpdf对中文字体的处理比TCPDF简单太多只要你把字体文件配置好直接用CSS指定font-family就能输出正常的中文PDF这对国内开发者几乎是刚需。所以最终我拍板用它。1.2 适用场景与选型判断mpdf适合什么场景我觉得可以列一个清单订单报表、发票打印、合同生成、数据导出、考试试卷打印、产品手册生成以及任何“界面已经用HTML画好了用户想要一键下载成PDF”的需求。它不太适合什么场景呢一个是超大文件批量生成比如一次导出几万行数据的PDF虽然可以通过设置临时目录和分批输出处理但性能和内存消耗始终是个瓶颈另一个是复杂的精确排版比如专业的杂志排版那种像素级控制这种还是得靠专业的排版工具。选型的时候我建议你把“中文字体支持”放在优先级很高的位置来考虑。很多PDF库在英文场景下跑得很好一遇到中文就乱码、方块字、缺字体处理起来非常痛苦。mpdf在这方面的成熟度是我最终选它的一个核心原因。如果你用下来发现自己需要频繁处理中文文档那mpdf基本不会让你失望。2. 核心细节解析与实操要点2.1 安装与环境准备mpdf的安装非常简单在项目根目录执行composer require mpdf/mpdf它需要PHP 5.6以上版本建议用7.x或8.x新版mpdf对PHP 8支持已经很好同时需要GD库和mbstring扩展。我用的服务器PHP版本是8.1下载安装后跑了个最简单的demo没有遇到任何兼容问题。如果你的环境是PHP 5.x那就得用mpdf的老版本推荐装一个版本锁定。composer require mpdf/mpdf:^8.0差不多就是这样。装完之后通过require vendor/autoload.php;引入Composer的自动加载文件就能实例化Mpdf对象了。2.2 中文字体配置mpdf的“第一道坎”这一步是mpdf最容易翻车的地方也是网上问得最多的。mpdf默认的字体里不包含中文字体如果你直接用默认配置输出中文出来的PDF里只会是一堆方块或者空白。解决办法就是给mpdf指定一个可用的中文字体文件。先准备字体文件。你可以用系统自带的SimHei黑体一般是simhei.ttf、SimSun宋体simsun.ttc也可以用开源免费的思源黑体Source Han Sans SC。我个人推荐思源黑体因为它是开源授权用在项目里不会有版权纠纷而且字形和显示效果也挺好。下载下来之后把.ttf或.otf文件放到你项目里的一个目录比如/public/fonts/或者/storage/fonts/。然后在生成PDF之前把字体注册给mpdf我用的代码如下$mpdf new \Mpdf\Mpdf([ mode utf-8, format A4, default_font simsun, fontDir [ __DIR__ . /storage/fonts/, ], fontdata [ simsun [ R simsun.ttc, I simsun.ttc, // 斜体 B simsun.ttc, // 加粗 BI simsun.ttc // 加粗斜体 ], ], ]);这里有几个参数要说清楚mode强制使用UTF-8编码这是中文不乱码的前提。format纸张大小常用的有A4、A5、Letter也可以传自定义数组如[80, 120]表示宽80mm高120mm的小票尺寸。default_font默认字体指定为上面注册的字体名称这样即使HTML里没有显式设置字体也会用这个字体渲染。fontDir字体文件所在目录。fontdata把字体文件注册成mpdf可以识别的字体族R代表Regular常规B代表Bold加粗I代表Italic斜体BI代表加粗斜体。如果你只有常规字体文件可以全部指向同一个文件。注册完之后你写的HTML里只要指定font-family: simsun;或者什么都不写走默认字体就能正常显示中文了。2.3 页面、页眉页脚与样式细节mpdf对页面的控制主要靠构造参数和链式方法。常用的构造参数除了刚才提到的format之外还有margin_left、margin_right、margin_top、margin_bottom这些用来设置页边距。如果你要自定义页眉页脚可以这样搞$mpdf-SetHeader(单据名称||{PAGENO}); $mpdf-SetFooter(第 {PAGENO} 页 / 共 {nb} 页);大括号里的{PAGENO}是当前页号{nb}是总页数mpdf会自动替换成实际数字。页眉页脚里还可以放图片比如公司Logo通过SetHeader传数组就能实现。如果你需要首页不加页眉页脚可以用SetHeader之后调用SetHeader设置不同奇偶页或首页不过我们实际项目里用默认的全页显示就够了。再说CSS支持。mpdf能识别CSS 2.1的大部分属性比如color、background-color、border、text-align、font-size、font-weight也支持float和部分position。但它对复杂Flexbox和Grid布局的支持基本为零所以你写HTML时不要用现代布局那套老老实实用表格加内联样式效果最稳定。这也是很多从Dompdf转过来的人需要注意的mpdf适合“古典Web”风格的HTML。3. 实操过程与核心环节实现3.1 典型场景订单数据导出为PDF光说理论没意思我拿一个实际做过的功能来完整走一遍。需求是这样的后台订单列表页面有个“导出PDF”按钮点击之后把当前筛选条件下的订单数据订单号、客户名称、产品明细、金额、下单时间生成一份PDF弹到浏览器直接下载。我分三步处理。第一步查询数据。$orders $orderModel-where($condition)-select();这块大家都会就不展开了重点在第二步。第二步拼HTML。为了便于维护我单独封装了一个方法来渲染表格行而不是在控制器里写一大坨拼接字符串。模板思路是这样$html style .order-table { width: 100%; border-collapse: collapse; font-size: 12px; } .order-table th, .order-table td { border: 1px solid #666; padding: 6px 8px; } .order-table th { background-color: #f0f0f0; text-align: center; } .amount { text-align: right; } /style; $html . h2 styletext-align:center;订单导出报告/h2; $html . p styletext-align:center;导出时间 . date(Y-m-d H:i:s) . /p; $html . table classorder-table; $html . theadtr th订单号/th th客户名称/th th产品明细/th th订单金额/th th下单时间/th /tr/thead; $html . tbody; foreach ($orders as $order) { // 这里拼接明细注意转义HTML $details ; foreach ($order[products] as $product) { $details . $product[name] . x . $product[qty] . ; } $details rtrim($details, ); $html . tr; $html . td . htmlspecialchars($order[order_no]) . /td; $html . td . htmlspecialchars($order[customer_name]) . /td; $html . td . htmlspecialchars($details) . /td; $html . td classamount . number_format($order[total_amount], 2) . /td; $html . td . $order[create_time] . /td; $html . /tr; } $html . /tbody/table;第三步实例化mpdf、渲染、输出。这是最核心的一段我分步骤来讲。3.2 关键代码剖析与参数说明require_once __DIR__ . /vendor/autoload.php; $mpdf new \Mpdf\Mpdf([ mode utf-8, format A4, default_font simsun, fontDir [__DIR__ . /storage/fonts/], fontdata [ simsun [ R simsun.ttc, I simsun.ttc, B simsun.ttc, BI simsun.ttc ] ], margin_left 15, margin_right 15, margin_top 20, margin_bottom 20, tempDir __DIR__ . /runtime/mpdf_temp, ]);tempDir是我后来才加上的配置。mpdf在处理PDF时会在临时目录里写文件默认走系统tmp目录但有些服务器对系统tmp目录做了权限限制就会报错。把它指到项目自己的runtime目录既能避开权限问题也方便排查异常。记得这个目录要有写权限。接下来设置页眉页脚然后写入HTML$mpdf-SetHeader(订单导出报告|——内部资料——|{PAGENO}); $mpdf-SetFooter(第 {PAGENO} 页 / 共 {nb} 页); // 写入HTML内容 $mpdf-WriteHTML($html); // 输出到浏览器并下载 $mpdf-Output(订单导出_ . date(YmdHis) . .pdf, D);Output的第二个参数有几种选择I内联输出到浏览器PDF会在浏览器里打开预览。D强制下载浏览器会直接弹出下载框。F保存到服务器本地文件。S返回PDF内容字符串方便你继续处理比如存数据库或发送邮件。我们导出场景用D最合适但要注意一点Output的文件名如果包含中文某些浏览器的下载文件名可能乱码。我后来改成传一个不带中文的文件名或者用rawurlencode处理一下基本就稳定了。还有一个小细节WriteHTML之前如果有多余的空白输出比如BOM头或者echo出来的空格会导致PDF生成失败报“headers already sent”类似的错误。这类问题排查起来很阴间建议在开发时打开PHP错误提示并且入口文件不要有任何多余输出。3.3 输出与下载的正确姿势再说说输出时容易踩的坑。Output方法一旦调用脚本就会结束后面写的代码都不再执行。如果你想在生成PDF之前做权限校验、日志记录、邮件发送一定要放在Output调用之前。另外有些同事喜欢把PDF内容先保存成文件再跳转给用户下载这样会有个隐患每次点击导出都会在服务器上留一个PDF文件时间长了碎片会越来越多。我当时设计了两种模式管理员需要留痕时用F保存到指定目录普通用户直接用D下载如果你不需要留痕优先用D。关于文件名的编解码我建议这样$fileName 订单导出_ . date(YmdHis) . .pdf; $encodedName rawurlencode($fileName); $mpdf-Output($encodedName, D);这样在大多数浏览器下都能正常显示中文文件名。如果还需要兼容比较老的IE内核就得更复杂一些但现在的项目一般不需要考虑这个了。4. 常见问题与排查技巧实录4.1 中文乱码类问题中文乱码是mpdf里问得最多的一个问题我把它排在最前面。乱码的表现一般有两类一类是PDF里中文全是方块或空白另一类是中文变成一堆乱码字符。第一类基本就是字体没配置好。你要检查三件事字体文件是否真的存在且路径正确fontdata里的字体名和文件是否对应HTML里指定的font-family是否正确。还有一种情况是字体文件本身损坏换个字体文件试一下。我遇到过最奇怪的一次是simsun.ttc在Windows下没问题部署到Linux服务器就乱码后来排查是字体文件权限不对PHP进程读不到加上可读权限就正常了。第二类乱码往往是编码问题。确保数据库连接使用UTF-8确保HTML输出前没有BOM头确保mode参数是utf-8。如果你是从老系统迁移过来的数据表字段可能是GBK编码那在拼HTML之前必须先转码$order[customer_name] mb_convert_encoding($order[customer_name], UTF-8, GBK);用mb_convert_encoding或者iconv都行关键是把所有数据统一成UTF-8再进模板。4.2 内存与性能问题mpdf比较吃内存这是它的一个短板。生成一份几十页的报表PHP脚本占用内存上百MB是很常见的事。如果你的服务器内存配置比较紧张或者说你要批量生成PDF就得注意优化。我的做法是先估算数据量。假如需要导出的订单有2000条每条订单还有明细那拼出来的HTML可能就有几百KB甚至上MB。这时候直接在内存里生成PDF就容易爆。优化策略有几种一种是限制单次导出数量比如一次最多导500条超过就提示用户分段导出另一种是开启tempDir并检查磁盘空间让mpdf把中间文件写到磁盘而不是全放内存还有一种是把PDF生成放到后台队列里用户点击导出后先去处理生成完再推送下载链接避免请求超时。如果你只是偶尔生成一两个PDF那默认配置完全够用。如果你要做批量任务建议你用命令行模式运行PHP脚本不受PHP的max_execution_time和memory_limit限制或很大稳定很多。我在项目里用Laravel的队列做过一次配合php artisan queue:work连续生成一百多份PDF都没出问题。4.3 输出、样式与其他问题速查遇到输出报错“headers already sent”先查入口文件的第一行是不是有BOM头或者多余空格。BOM头是老问题了很多编辑器默认会加用VS Code或Sublime这类现代编辑器基本不会遇到但Windows自带的记事本就会加。解决办法是用编辑器把文件另存为UTF-8 without BOM。样式不生效也是高频问题。mpdf对CSS的支持虽然在PHP库里算强的但和浏览器比还是有差距。float在mpdf里能用但如果浮动元素后面跟着大段文本偶尔会出现错位。我在实际项目里总结的经验是PDF模板尽量用table布局不要用div加float边框用border-collapse: collapse间距用padding字体大小用pt或px都行1pt约等于1.333px。遇到渲染差异最直接的调试方式是先把HTML在浏览器里打开看效果再交给mpdf渲染对比哪里不一样然后针对性地调整CSS。mpdf还提供了一个调试模式可以在构造参数里加debug true它会输出一些渲染相关的信息帮你看清楚问题出在哪。表格跨页时表头默认不会重复如果你希望表头在每一页都显示mpdf有一个thead的自动处理机制只要在HTML里用thead包裹表头行跨页时它会自动重复。这个功能实测下来很好用不用额外配置。图片不显示也是个常见问题。mpdf里引用图片路径必须是服务器本地绝对路径或者可访问的URL。如果你用相对路径比如img srcimages/logo.pngmpdf会根据当前工作目录去找很容易找不到。我习惯在拼HTML之前把图片路径转成绝对URL$logoPath $_SERVER[DOCUMENT_ROOT] . /uploads/logo.png; $html img src . $logoPath . width120;这样就不会有路径问题了。最后再分享一个独家技巧如果你需要把多个HTML片段合并到一个PDF里比如封面是一段HTML、内容表格是另一段HTML可以使用mpdf的WriteHTML方法多次调用它会在同一个PDF里追加内容。如果你想分页在HTML里插入一个分页符就行div stylepage-break-before: always;/div或者直接调用$mpdf-AddPage();手动翻页。这两种方式按需选择实际用下来都很顺手。在我这几年用mpdf做PDF导出这个方向上最大的体会是别把它当浏览器去要求它就是“用HTML语法来描述PDF版面”的工具理解了这一点很多样式上的问题就不会纠结了。你只要牢牢记住“中文字体先注册HTML结构用表格图片路径用绝对地址”这三条铁律基本就能稳稳地跑通整个流程。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价