最稳方案是用PhpSpreadsheet实现导出接口,需严格遵循「数据查完再写入」「响应头配对」「类型/字体显式控制」三原则;响应头必须含Content-Type(带charset=utf-8)、Content-Disposition(中文文件名需rawurlencode)、Cache-Control;大表导出须手动循环setCellValue并每千行gc_collect_cycles();日期用setValueExplicit+NumberFormat,中文显示需setDefaultStyle字体为SimSun或Microsoft YaHei。

直接用 PhpSpreadsheet 实现导出接口最稳,别碰 ThinkPHP 自带的 Excel 扩展(已废弃)或老版 PHPExcel(不维护、有安全风险)。接口核心是「数据查完再写入」+「响应头配对」+「类型/字体显式控制」,三者缺一不可。
导出接口必须设对的三个响应头
少一个就可能下载失败、乱码或被浏览器拦截:
-
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=utf-8—— 必须带charset=utf-8,否则中文列头在 Excel 里显示为方块 -
Content-Disposition: attachment; filename="<name>.xlsx"—— 文件名含中文时,要用rawurlencode()包一层,比如rawurlencode('用户报表.xlsx') -
Cache-Control: max-age=0—— 防止某些代理或浏览器缓存旧文件导致下载内容错乱
大表导出(>5000 行)不能用 fromArray()
fromArray() 会把所有数据转成字符串,丢掉数值、日期等原始类型,且一次性加载进内存,极易触发 Allowed memory size exhausted。正确做法是手动循环写入 + 主动回收:
- 用
$sheet->setCellValue('A'.$row, $value)逐行写,不走批量 - 每写 1000 行调一次
gc_collect_cycles(),释放 PHP 内存 - 日期字段别用
$date->format('Y-m-d')转字符串塞进去;改用setValueExplicit($timestamp, DataType::TYPE_NUMERIC)+setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2)
中文显示为方块或 #VALUE!?检查字体设置
Excel 默认字体不支持 UTF-8 渲染,必须显式指定中文字体,且要设在 getDefaultStyle() 上:
立即学习“PHP免费学习笔记(深入)”;
- 加这行:
$spreadsheet->getDefaultStyle()->getFont()->setName('SimSun');(Windows 推荐)或'Microsoft YaHei' - 别只设单元格字体,全局默认样式没设,新插入的行仍会用 Arial 导致乱码
- 如果部署在 Linux 服务器,确认系统已安装对应字体,否则 fallback 到无字形,照样显示方块
最容易被忽略的是:导出前的数据查询必须完成,不能边查边写;日期和数字类型必须用 setValueExplicit + 格式码双写,靠 PHP 字符串格式化等于放弃 Excel 的原生能力。



















