1. 程式人生 > 其它 >PHP基於xlswriter支援無限表頭層級Excel匯出

PHP基於xlswriter支援無限表頭層級Excel匯出

本文介紹基於PHP擴充套件xlswriter的Vtiful\Kernel\Excel類可以支援無限層級的複雜表頭匯出!

一、準備xlswriter擴充套件

1、windows系統:

到PECL網站下載符合自己本地PHP環境的ddl檔案下載地址:https://pecl.php.net/package/xlswriter,並複製到PHP的擴充套件目錄ext資料夾下,修改php.ini檔案,

加上這行

extension=xlswriter

 開啟phpinfo()驗證擴充套件是否安裝成功

 2、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)); } /** * 設定檔名 * @param string $fileName 檔名 * @param string $sheetName 第一個sheet名 */ 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())); } /** * 設定表頭 * @param array $header * @param bool $filter * @throws Exception */ 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); } /** * 填充檔案資料 * @param array $data */ public function setData(array $data) { foreach ($data as $row => $datum) { foreach ($datum as $column => $value) { $this->fileObject->insertText($row + $this->maxHeight, $column, $value); } } } /** * 新增Sheet * @param string $sheetName */ public function addSheet(string $sheetName) { $this->fileObject->addSheet($sheetName); } /** * 儲存檔案至伺服器 */ public function output() { return $this->fileObject->output(); } /** * 輸出到瀏覽器 * @param $filePath * @param $fileName * @throws Exception */ 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(); } /** * 組裝單元格合併需要的資訊 * @param $header * @param int $cursor * @param int $col * @param array $colManage * @param $parentList * @param $parent * @throws \Exception * @return array */ 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; } /** * 完善單元格合併資訊 * @param $colManage * @return array */ 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; } /** * 合併單元格 * @param $colManage * @param $filter */ 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}"); } } /** * 獲取單元格列標識 * @param $num * @return string */ 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',
            ],
        ];
// header頭規則 title表示列標題,children表示子列,沒有子列children可不寫或為空
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); //這裡可以使用新的header
$xlsWriterServer->setData($data); // 這裡也可以根據新的header定義資料格式

$filePath = $xlsWriterServer->output(); // 儲存到伺服器
$xlsWriterServer->excelDownload($filePath); // 輸出到瀏覽器

匯出效果圖: