共计 13320 个字符,预计需要花费 34 分钟才能阅读完成。
网上其实有很多教程,但这些教程很多都差强人意,这里并非恶意贬低或者诋毁他人,只是客观的陈述。
比如这篇相对来说已经写的算是很不错: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 引用 PhpOfficePhpSpreadsheet,当然也可以不引用,需要使用的地方直接使用即可。
1
2
|
use PhpOfficePhpSpreadsheetWorksheetDrawing;
use PhpOfficePhpSpreadsheetIOFactory;
|
如何载入模板文件
以下代码以 TP6 为实例,请根据自身情况作适当调整。
定义模板路径:
1
|
$inputFileName = public_path().‘/static/template/’.‘YanghuShigong.xlsx’;
|
在 TP6 中,public_path()指向的是 public 目录,所以需要对应路径上传好我们的模板文件。
加载模板文件:
1
|
$spreadsheet = PhpOfficePhpSpreadsheetIOFactory::load($inputFileName);
|
如果填写模板
模板的填写分为两种情况,一种是把 Excel 当固定表格使用,这样只需依次替换各个单元格的值即可,另一种则是渲染列表数据批量插入到 excel 中。
情况一,依次填写各个单元格:
这种情况非常之简单,只需如是操作即可。
1
2
3
4
5
6
7
8
9
10
11
12
|
$spreadsheet = PhpOfficePhpSpreadsheetIOFactory::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 PhpOfficePhpSpreadsheetWorksheetDrawing();
$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 = PhpOfficePhpSpreadsheetIOFactory::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 表格 的完整过程已经说明完毕,大致说来非常简单:
定义模板 ->加载模板路径 ->依次渲染填写数据 ->导出或保存表格
希望能有所帮助。