网上其实有很多教程,但这些教程很多都差强人意,这里并非恶意贬低或者诋毁他人,只是客观的陈述。
比如这篇相对来说已经写的算是很不错:https://blog.csdn.net/u013224364/article/details/125786382
然而通篇没有告诉你怎么安装什么扩展,自己嘻嘻哈哈了半天,读者是一头雾水。
诚然,如果有做过类似项目的自然能从代码中读到是用了PHPExcel,但PHPExcel本身就一个问题,如下图:
官方已经说明了项目已经在2017年正式弃用,PHPExcel – DEAD 这句话写的很清楚。令人难以置信的是,2022年2023年仍然有文章在继续介绍如何使用。
我并不反对使用一个陈旧的库,但是在正式开发的商业项目中,使用一个别人已经明确说明在8年前死亡的拓展并给出了迁移指南的情况下,是否可能会让整个项目承担一些不可预估的风险?
言归正传,下面开始详细的说一下,在PHP中,怎么导出一个基于excel模板的excel文件。
准备工作
在你的PHP项目中,安装PHPExcel中推荐的 PhpSpreadsheet ,这个项目的地址是:
https://github.com/PHPOffice/PhpSpreadsheet
1 |
composer require phpoffice/phpspreadsheet |
手动添加的话则是在composer.json中添加:
1 2 3 4 5 6 7 8 9 10 |
{ "require": { "phpoffice/phpspreadsheet": "^1.28" }, "config": { "platform": { "php": "7.4" } } } |
随后执行安装
1 |
composer install |
二则选其一进行即可。
使用PhpSpreadsheet
在你的项目文件中,按需use引用PhpOffice\PhpSpreadsheet,当然也可以不引用,需要使用的地方直接使用即可。
1 2 |
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\IOFactory; |
如何载入模板文件
以下代码以TP6为实例,请根据自身情况作适当调整。
定义模板路径:
1 |
$inputFileName = public_path().'/static/template/'.'YanghuShigong.xlsx'; |
在TP6中,public_path()指向的是public目录,所以需要对应路径上传好我们的模板文件。
加载模板文件:
1 |
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($inputFileName); |
如果填写模板
模板的填写分为两种情况,一种是把Excel当固定表格使用,这样只需依次替换各个单元格的值即可,另一种则是渲染列表数据批量插入到excel中。
情况一,依次填写各个单元格:
这种情况非常之简单,只需如是操作即可。
1 2 3 4 5 6 7 8 9 10 11 12 |
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($inputFileName); $spreadsheet->getActiveSheet()->setCellValue('B2', $info['order_id']); $spreadsheet->getActiveSheet()->setCellValue('E2', $info['order_date']); $spreadsheet->getActiveSheet()->setCellValue('B3', $info['alarm_time']); $spreadsheet->getActiveSheet()->setCellValue('D3', $info['alarm_people']); $spreadsheet->getActiveSheet()->setCellValue('F3', $info['depart_time']); $spreadsheet->getActiveSheet()->setCellValue('B4', $info['evacuate_time']); $spreadsheet->getActiveSheet()->setCellValue('D4', $info['depart_car']); $spreadsheet->getActiveSheet()->setCellValue('B5', $info['depart_people']); $spreadsheet->getActiveSheet()->setCellValue('E5', $info['stake_number']); $spreadsheet->getActiveSheet()->setCellValue('B7', $info['treatment_measure']); $spreadsheet->getActiveSheet()->setCellValue('B8', $info['scene_detail']); |
类似上面的代码,依次填写各个单元格,合并单元格的情况也只需要任意被合并的单元格即可。
情况二,$list数据依次插入到表格中:
这种情况稍显复杂,为了不破坏样式,我们要做的是插入新行。
一般来说,我们在制作模板的时候,都会预留了一些空白行以供填写数据,那么只有在填写的数据行数大于预留的数据行数时,我们才需要插入新行,可通过如下代码实现。
这里假设我们预留了10行数据,只有当$list中的数据大于10行时,才会执行插入。
1 2 3 4 |
if(count($list)>10){ // 插入新的行 $spreadsheet->getActiveSheet()->insertNewRowBefore(7, 10-count($list)); } |
随后是依次插入各行的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 定义数据初始行 $row = 5; for ($i=0; $i < count($list) ; $i++) { $spreadsheet->getActiveSheet()->setCellValue('A'.($row + $i), $i+1); $spreadsheet->getActiveSheet()->setCellValue('B'.($row + $i), $list[$i]['order_date']); $spreadsheet->getActiveSheet()->setCellValue('D'.($row + $i), $list[$i]['alarm_people']); $spreadsheet->getActiveSheet()->setCellValue('E'.($row + $i), $list[$i]['depart_time']); $spreadsheet->getActiveSheet()->setCellValue('G'.($row + $i), $list[$i]['depart_people']); $spreadsheet->getActiveSheet()->setCellValue('H'.($row + $i), $list[$i]['stake_number']); $spreadsheet->getActiveSheet()->setCellValue('I'.($row + $i), $list[$i]['depart_car']); $spreadsheet->getActiveSheet()->setCellValue('K'.($row + $i), $list[$i]['scene_detail']); $spreadsheet->getActiveSheet()->setCellValue('L'.($row + $i), $list[$i]['evacuate_time']); } |
如何插入图片
官方文档关于这部分的说明在:https://phpspreadsheet.readthedocs.io/en/latest/topics/recipes/#add-a-drawing-to-a-worksheet
这里以多行数据插入的情况来做个简单示例,单张图片相对简单,对应调整或参照官方文档操作即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
for ($i=0; $i < count($list) ; $i++) { $spreadsheet->getActiveSheet()->setCellValue('A'.($row + $i), $i+1); $spreadsheet->getActiveSheet()->setCellValue('B'.($row + $i), $list[$i]['patrol_stake_number']); $spreadsheet->getActiveSheet()->setCellValue('C'.($row + $i), $list[$i]['patrol_project_name']); $spreadsheet->getActiveSheet()->setCellValue('D'.($row + $i), $list[$i]['patrol_path']); $spreadsheet->getActiveSheet()->setCellValue('E'.($row + $i), $list[$i]['patrol_time']); $spreadsheet->getActiveSheet()->setCellValue('F'.($row + $i), '凯环北高速公路运管中心养护工区'); $spreadsheet->getActiveSheet()->setCellValue('H'.($row + $i), $list[$i]['patrol_content_people_num']); $spreadsheet->getActiveSheet()->setCellValue('I'.($row + $i), $list[$i]['patrol_content_kil']); $spreadsheet->getActiveSheet()->setCellValue('J'.($row + $i), $list[$i]['patrol_content_fuel']); $spreadsheet->getActiveSheet()->setCellValue('K'.($row + $i), $list[$i]['patrol_content_toll']); $spreadsheet->getActiveSheet()->setCellValue('M'.($row + $i), $list[$i]['patrol_content_expenditure']); //判断图片字段中是否存在图片 if($list[$i]['patrol_files']){ // 创建图片对象 $drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); $drawing->setWorksheet($spreadsheet->getActiveSheet()); $drawing->setName('before'); $drawing->setDescription('before image'); $drawing->setPath(public_path().(str_ireplace($this->baseUrl, '', $list[$i]['patrol_files'][0]))); $drawing->setHeight(90); $drawing->setCoordinates('N'.($row + $i)); // 设置行高 $spreadsheet->getActiveSheet()->getRowDimension($row + $i)->setRowHeight(90, 'px'); } } |
通过实例化Drawing对象来实现图片的插入,setPath中只需要描述图片的路径即可,上面的代码因为数据库中有多张图片,只取第一张所以这样写,实际情况中可以这样简写:
1 |
$drawing->setPath('./images/paid.png'); |
$drawing->setHeight是设置图片的高度;
$drawing->setCoordinates是插入的位置;
通过setRowHeight设置单元格高度和图片高度一致,使得excel更为美观。
如果是高宽比不确定的情况下,则还要定义宽度:
$drawing->setWidth(310);
插入图片的特殊情况
有一些图片数据,比如电子签名,是以base64的形式存储在数据中,则不能直接传递给Drawing对象来插入,否则可能会报错。
我们需要将base64图片转换为实体图片,存储在临时文件夹,再赋值给Drawing对象,这样能保证整个过程不会出错。
转换过程如下:
1 2 3 4 5 |
// 签名图片保存位为实体文件 $image = explode(',',($info['station_signature']))[1]; $imageSrc= public_path()."storage/temp/". time().rand(100,999).'.jpg'; //拼接路径和图片名称 $r = file_put_contents($imageSrc, base64_decode($image));//生成图片 返回的是字节数 $drawing->setPath($imageSrc); |
保存导出文件
经过以上操作之后,我们已经处理好了整个表格,接下来是导出。
导出也分为两大类,一类是PHP直接输出,另一类是先保存在本地,给前端传递文件路径下载。
PHP直接输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
private function downloadExcel($newExcel,$filename,$format) { ob_end_clean(); ob_start(); // $format只能为 Xlsx 或 Xls if ($format == 'Xlsx') { header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); } elseif ($format == 'Xls') { header('Content-Type: application/vnd.ms-excel'); } // strtolower($format) header("Content-Disposition: attachment;filename=" . $filename . '.' . strtolower($format)); header('Cache-Control: max-age=0'); $objWriter = IOFactory::createWriter($newExcel, $format); $objWriter->save('php://output'); //通过php保存在本地的时候需要用到 // $objWriter->save($dir.'/demo.xlsx'); //以下为需要用到IE时候设置 // If you're serving to IE 9, then the following may be needed //header('Cache-Control: max-age=1'); // If you're serving to IE over SSL, then the following may be needed //header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past //header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified //header('Cache-Control: cache, must-revalidate'); // HTTP/1.1 //header('Pragma: public'); // HTTP/1.0 exit; } |
其实就是定义Header,然后执行即可,上面这个方法来自:https://blog.csdn.net/weixin_47736740/article/details/127802751
传递给前端:
相比较而言,我更喜欢这种方式,因为文件可以得到服用,查历史文件记录也方便,路径给到前端,前端怎么处理也都是可以的,所以宽容度会更高。
1 2 3 4 |
// 保存文件 $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx'); $writer->save(public_path().'storage/templateOut/突发事件处置记录表('.$monthScope[0].'-'.$monthScope[1].').xlsx'); $this->ok(Request::domain().'/storage/templateOut/突发事件处置记录表('.$monthScope[0].'-'.$monthScope[1].').xlsx'); |
$this->ok 是我自己定义的方法,根据自己的实际输出路径给前端即可。
至此,PHP 中通过excel模板文件导出excel表格的完整过程已经说明完毕,大致说来非常简单:
定义模板->加载模板路径->依次渲染填写数据->导出或保存表格
希望能有所帮助。
原创文章,作者:蓝洛水深,如若转载,请注明出处:https://blog.lanluo.cn/11999