本章介绍基于PHP扩展xlswriter的Vtiful\Kernel\Excel类可以支持无限层级的复杂表头导出! 废了九牛二虎之力,终于把这个功能类写完了…后续会持续更新优化
准备xlswriter扩展
windows系统: 到PECL网站下载符合自己本地PHP环境的ddl文件下载地址,并复制到PHP的扩展目录ext文件夹下,修改php.ini文件,加上这行
extension=xlswriter
打开phpinfo()验证扩展是否安装成功 Linux系统:
pecl install xlswriter
php配置文件添加
extension = xlswriter.so
重启php nginx
composer下载phpoffice/phpexcel
因为有用到单元格相关函数,所以需要执行下列命令
composer require phpoffice/phpexcel 1.8
封装导出类文件(重点来了)
<?php
use PHPExcel_Cell;
class MultiFloorXlsWriterService
{
private $defaultWidth = 16;
private $exportType = '.xlsx';
private $maxHeight = 1;
private $fileName = null;
private $xlsObj;
private $fileObject;
private $format;
public function __construct()
{
$path = base_path().'/storage/logs';
$config = [
'path' => $path
];
$this->xlsObj = (new \Vtiful\Kernel\Excel($config));
}
public function setFileName(string $fileName = '', string $sheetName = 'Sheet1')
{
$fileName = empty($fileName) ? (string)time() : $fileName;
$fileName .= $this->exportType;
$this->fileName = $fileName;
$this->fileObject = $this->xlsObj->fileName($fileName, $sheetName);
$this->format = (new \Vtiful\Kernel\Format($this->fileObject->getHandle()));
}
public function setHeader(array $header, bool $filter = false)
{
if (empty($header)) {
throw new \Exception('表头数据不能为空');
}
if (is_null($this->fileName)) {
self::setFileName(time());
}
$colManage = self::setHeaderNeedManage($header);
$colManage = self::completeColMerge($colManage);
self::queryMergeColumn($colManage, $filter);
}
public function setData(array $data)
{
foreach ($data as $row => $datum) {
foreach ($datum as $column => $value) {
$this->fileObject->insertText($row + $this->maxHeight, $column, $value);
}
}
}
public function addSheet(string $sheetName)
{
$this->fileObject->addSheet($sheetName);
}
public function output()
{
return $this->fileObject->output();
}
public function excelDownload($filePath)
{
$fileName = $this->fileName;
$userBrowser = $_SERVER['HTTP_USER_AGENT'];
if( preg_match('/MSIE/i', $userBrowser)) {
$fileName = urlencode($fileName);
} else {
$fileName = iconv('UTF-8', 'GBK//IGNORE', $fileName);
}
header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
header('Content-Disposition: attachment;filename="' . $fileName . '"');
header('Content-Length: ' . filesize($filePath));
header('Content-Transfer-Encoding: binary');
header('Cache-Control: must-revalidate');
header('Cache-Control: max-age=0');
header('Pragma: public');
if (ob_get_contents()) {
ob_clean();
}
flush();
if (copy($filePath, 'php://output') === false) {
throw new \Exception($filePath. '地址出问题了');
}
@unlink($filePath);
exit();
}
private function setHeaderNeedManage($header, $col = 1, &$cursor = 0, &$colManage = [], $parent = null, $parentList = [])
{
foreach ($header as $head) {
if (empty($head['title'])) {
throw new \Exception('表头数据格式有误');
}
if (is_null($parent)) {
$parentList = [];
$col = 1;
} else {
foreach ($colManage as $value) {
if ($value['parent'] == $parent) {
$parentList = $value['parentList'];
$col = $value['height'];
break;
}
}
}
$column = $this->getColumn($cursor) . $col;
$colManage[$column] = [
'title' => $head['title'],
'cursor' => $cursor,
'cursorEnd' => $cursor,
'height' => $col,
'width' => $this->defaultWidth,
'mergeStart' => $column,
'hMergeEnd' => $column,
'zMergeEnd' => $column,
'parent' => $parent,
'parentList' => $parentList,
];
if (isset($head['children']) && !empty($head['children']) && is_array($head['children'])) {
$col += 1;
$parentList[] = $column;
$this->setHeaderNeedManage($head['children'], $col, $cursor,$colManage, $column, $parentList);
} else {
$cursor += 1;
}
}
return $colManage;
}
private function completeColMerge($colManage)
{
$this->maxHeight = max(array_column($colManage, 'height'));
$parentManage = array_column($colManage, 'parent');
foreach ($colManage as $index => $value) {
if (!is_null($value['parent']) && !empty($value['parentList'])) {
foreach ($value['parentList'] as $parent) {
$colManage[$parent]['hMergeEnd'] = self::getColumn($value['cursor']) . $colManage[$parent]['height'];
$colManage[$parent]['cursorEnd'] = $value['cursor'];
}
}
$checkChildren = array_search($index, $parentManage);
if ($value['height'] < $this->maxHeight && !$checkChildren) {
$colManage[$index]['zMergeEnd'] = self::getColumn($value['cursor']) . $this->maxHeight;
}
}
return $colManage;
}
private function queryMergeColumn($colManage, $filter)
{
foreach ($colManage as $value) {
$this->fileObject->mergeCells("{$value['mergeStart']}:{$value['zMergeEnd']}", $value['title']);
$this->fileObject->mergeCells("{$value['mergeStart']}:{$value['hMergeEnd']}", $value['title']);
if ($value['cursor'] != $value['cursorEnd']) {
$value['width'] = ($value['cursorEnd'] - $value['cursor'] + 1) * $this->defaultWidth;
}
$toColumnStart = self::getColumn($value['cursor']);
$toColumnEnd = self::getColumn($value['cursorEnd']);
$this->fileObject->setColumn("{$toColumnStart}:{$toColumnEnd}", $value['width']);
}
if ($filter) {
$filterEndColumn = self::getColumn(end($colManage)['cursorEnd']) . $this->maxHeight;
$this->fileObject->autoFilter("A1:{$filterEndColumn}");
}
}
private function getColumn($num)
{
return PHPExcel_Cell::stringFromColumnIndex($num);
}
}
使用示例
$header = [
[
'title' => '一级表头1',
'children' => [
[
'title' => '二级表头1',
],
[
'title' => '二级表头2',
],
[
'title' => '二级表头3',
],
]
],
[
'title' => '一级表头2'
],
[
'title' => '一级表头3',
'children' => [
[
'title' => '二级表头1',
'children' => [
[
'title' => '三级表头1',
],
[
'title' => '三级表头2',
],
]
],
[
'title' => '二级表头2',
],
[
'title' => '二级表头3',
'children' => [
[
'title' => '三级表头1',
'children' => [
[
'title' => '四级表头1',
'children' => [
[
'title' => '五级表头1'
],
[
'title' => '五级表头2'
]
]
],
[
'title' => '四级表头2'
]
]
],
[
'title' => '三级表头2',
],
]
]
]
],
[
'title' => '一级表头4',
],
[
'title' => '一级表头5',
],
];
for ($i = 0; $i < 100; $i++) {
$data[] = [
'这是第'. $i .'行测试',
'这是第'. $i .'行测试',
'这是第'. $i .'行测试',
'这是第'. $i .'行测试',
'这是第'. $i .'行测试',
'这是第'. $i .'行测试',
'这是第'. $i .'行测试',
'这是第'. $i .'行测试',
'这是第'. $i .'行测试',
'这是第'. $i .'行测试',
'这是第'. $i .'行测试',
'这是第'. $i .'行测试',
'这是第'. $i .'行测试',
];
}
$fileName = '很厉害的文件导出类';
$xlsWriterServer = new MultiFloorXlsWriterService();
$xlsWriterServer->setFileName($fileName, '这是Sheet1别名');
$xlsWriterServer->setHeader($header, true);
$xlsWriterServer->setData($data);
$xlsWriterServer->addSheet('这是Sheet2别名');
$xlsWriterServer->setHeader($header);
$xlsWriterServer->setData($data);
$filePath = $xlsWriterServer->output();
$xlsWriterServer->excelDownload($filePath);
导出效果图:
|