『2023版』详解PHP通过Excel模板导出Excel

374次阅读

共计 13320 个字符,预计需要花费 34 分钟才能阅读完成。

网上其实有很多教程,但这些教程很多都差强人意,这里并非恶意贬低或者诋毁他人,只是客观的陈述。

比如这篇相对来说已经写的算是很不错:https://blog.csdn.net/u013224364/article/details/125786382

然而通篇没有告诉你怎么安装什么扩展,自己嘻嘻哈哈了半天,读者是一头雾水。

诚然,如果有做过类似项目的自然能从代码中读到是用了PHPExcel,但 PHPExcel 本身就一个问题,如下图:

『2023 版』详解 PHP 通过 Excel 模板导出 Excel

官方已经说明了项目已经在 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;

如何载入模板文件

『2023 版』详解 PHP 通过 Excel 模板导出 Excel

以下代码以 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, 10count($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 是我自己定义的方法,根据自己的实际输出路径给前端即可。

『2023 版』详解 PHP 通过 Excel 模板导出 Excel

至此,PHP 中通过 excel 模板文件导出 excel 表格 的完整过程已经说明完毕,大致说来非常简单:

定义模板 ->加载模板路径 ->依次渲染填写数据 ->导出或保存表格

希望能有所帮助。

正文完
 0