Laravel Excel导出PNG图片失败:仅显路径、仅导出单张图
问题核心原因
- 初版导出仅写入图片路径字符串:Laravel Excel默认不会自动将单元格内的路径字符串解析为嵌入图片,因此只会显示文本路径。
- 实现WithDrawings接口后仅导出1张图,本质是三个实现错误:
drawings()方法仅实例化了单个Drawing对象,没有为每一行数据生成独立的Drawing实例,底层PhpSpreadsheet会以最后一次赋值的坐标覆盖之前的配置,最终只显示1张图- 没有按行号动态计算每个图片对应的单元格坐标,所有图片要么叠在第一行位置,要么因坐标冲突被覆盖
- 图片路径使用了相对路径/网络路径,除第一张路径命中的图片外,其余图片因文件找不到被自动跳过
代码遗漏点清单
- 未在遍历数据时维护行号与图片的映射关系,图片坐标和数据行错位
- 复用单个Drawing实例给多行赋值,导致坐标配置互相覆盖
- 图片列未留空,写入的路径字符串和插入的图片重叠
- 未给数据行设置适配图片高度的行高,图片被截断
- 未做图片存在性校验,单张图片丢失就会中断整个导出流程
修复实现方案
确保已安装maatwebsite/excel:^3.1版本,直接替换导出类代码即可:
<?php namespace App\Exports; use App\Models\Signature; use Maatwebsite\Excel\Concerns\FromCollection; use Maatwebsite\Excel\Concerns\WithHeadings; use Maatwebsite\Excel\Concerns\WithDrawings; use Maatwebsite\Excel\Concerns\WithStyles; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Style\Alignment; class SignatureExport implements FromCollection, WithHeadings, WithDrawings, WithStyles { /** * 待插入的所有图片实例集合 * @var Drawing[] */ protected array $drawings = []; /** * 签名图片所在列 * @var string */ protected string $imageColumn = 'D'; /** * 表头占1行,数据从第2行开始 * @var int */ protected int $dataStartRow = 2; public function collection() { // 查全量签名数据 $signatures = Signature::all(); // 遍历数据逐行生成对应图片实例 foreach ($signatures as $index => $signature) { $currentRow = $this->dataStartRow + $index; // 拼接图片绝对物理路径,不要用url/asset生成的网络地址 $imageAbsolutePath = storage_path('app/public/' . ltrim($signature->sign_path, '/')); // 图片不存在直接跳过,避免导出中断 if (!file_exists($imageAbsolutePath)) { continue; } // 必须为每行new全新的Drawing实例,禁止复用对象 $drawing = new Drawing(); $drawing->setName(sprintf('签名_%s', $signature->id)); $drawing->setPath($imageAbsolutePath); // 绑定当前行对应单元格坐标 $drawing->setCoordinates($this->imageColumn . $currentRow); // 设置边距偏移,避免图片贴单元格边框 $drawing->setOffsetX(5); $drawing->setOffsetY(5); // 统一图片尺寸,等比缩放 $drawing->setHeight(60); $drawing->setResizeProportional(true); $this->drawings[] = $drawing; } // 返回导出数据,图片列返回空字符串即可 return $signatures->map(function ($signature) { return [ 'id' => $signature->id, 'signer_name' => $signature->signer_name, 'sign_time' => $signature->sign_time?->format('Y-m-d H:i:s'), 'sign_image' => '', // 图片列留空,由drawings接口插入图片 'remark' => $signature->remark ]; }); } public function headings(): array { return [ '记录ID', '签署人姓名', '签署时间', '手写签名', '备注' ]; } public function drawings(): array { return $this->drawings; } public function styles(Worksheet $sheet) { $totalRows = Signature::count() + $this->dataStartRow; // 逐行设置行高,适配图片高度 for ($row = $this->dataStartRow; $row <= $totalRows; $row++) { $sheet->getRowDimension($row)->setRowHeight(70); } // 图片列设置垂直居中 $sheet->getStyle(sprintf('%s:%s', $this->imageColumn, $this->imageColumn)) ->getAlignment() ->setVertical(Alignment::VERTICAL_CENTER); } }
控制器导出方法保持原有逻辑即可,不需要额外加响应头:
<?php namespace App\Http\Controllers; use App\Exports\SignatureExport; use Maatwebsite\Excel\Facades\Excel; class ExportController extends Controller { public function exportSignature() { $fileName = sprintf('签名记录导出_%s.xlsx', date('YmdHis')); return Excel::download(new SignatureExport(), $fileName); } }
避坑提醒
- 不要在
drawings()方法内查询数据生成图片实例:该方法在导出流程中会被多次调用,在collection遍历阶段生成图片实例可以避免重复查库、行号错位问题 - 绝对禁止复用Drawing实例:PhpSpreadsheet底层会以对象最后一次set的坐标为准,复用对象会导致所有图片叠在同一个单元格
- 图片路径必须用本地绝对路径:Drawing类不支持直接读取远程网络图片,远程图片需要先下载到本地临时目录再插入
- 不要在图片列单元格写入内容:插入的图片会悬浮在单元格上方,写入的路径/文字会显示在图片下方
内容的提问来源于stack exchange,提问作者JUSTIN CHUA
相关产品推荐
相关产品推荐

