文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

PHP基于xlswriter支持无限表头层级Excel导出

2023-09-03 20:46

关注

本章介绍基于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

封装导出类文件(重点来了)

 \PHPExcel_Style_NumberFormat::FORMAT_GENERAL,        'text' => \PHPExcel_Style_NumberFormat::FORMAT_TEXT,    ];    // 支持的单元列操作-数据合并    const CELL_ACT_MERGE = 'merge';    // 支持的单元列操作-背景颜色    const CELL_ACT_BACKGROUND = 'background';    // 数据合并开始标识    const ACT_MERGE_START = 'start';    // 数据合并结束标识    const ACT_MERGE_END = 'end';    private $allowCellActs = [        self::CELL_ACT_MERGE,        self::CELL_ACT_BACKGROUND,    ];    // 单元格操作集合    private $cellActs = [];    private $xlsObj;    private $fileObject;    private $format;    private $boldIStyle;    private $colManage;    private $lastColumnCode;    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)    {        if (empty($header)) {            throw new \Exception('表头数据不能为空');        }        if (is_null($this->fileName)) {            self::setFileName(time());        }        // 获取单元格合并需要的信息        $colManage = self::setHeaderNeedManage($header);        // 完善单元格合并信息        $this->colManage = self::completeColMerge($colManage);        // 设置最后单元格标识        $this->lastColumnCode = self::getColumn(end($this->colManage)['cursorEnd']) . $this->maxHeight;        // 合并单元格        self::queryMergeColumn();    }        public function setData(array $data)    {        // 起始行        $indexRow = $this->maxHeight + 1;        // 起始列        $indexCol = 0;        foreach ($data as $row => $datum) {            foreach ($datum as $column => $value) {                // 列值为数组,说明有额外操作                if (is_array($value)) {                    $val = $value[0];                    $act = $value[1];                    $pos = self::getColumn($indexCol) . $indexRow;                    // 有效行为                    $availableActs = array_intersect($this->allowCellActs, array_keys($act));                    foreach ($availableActs as $availableAct) {                        switch ($availableAct) {case self::CELL_ACT_MERGE:    // 数据合并    $this->cellActs[$indexCol][self::CELL_ACT_MERGE][$act[$availableAct]] = $pos;    $this->cellActs[$indexCol][self::CELL_ACT_MERGE]['val'] = $val;    break;case self::CELL_ACT_BACKGROUND:    // 背景颜色    $this->cellActs[$indexCol][self::CELL_ACT_BACKGROUND][] = [        'row'    => $row,        'column' => $column,        'color'  => $act[$availableAct],        'val'    => $val    ];    break;default:    throw new \Exception('不支持的单元格操作['. $availableAct .']');                        }                    }                } else {                    $this->fileObject->insertText($row + $this->maxHeight, $column, $value);                }                $indexCol++;            }            $indexRow++;            $indexCol = 0;        }        // 执行单元格操作        self::queryCellActs();        $this->maxDataLine = $this->maxHeight + count($data);    }        public function addSheet(string $sheetName)    {        $this->fileObject->addSheet($sheetName);    }        public function setFormula(array $formulas)    {        if (empty($formulas)) {            throw new \Exception('公式格式错误');        }        $line = $this->maxDataLine + $this->defaultFormulaTop;        foreach ($formulas as $formula) {            if (isset($formula['col_title']) && isset($formula['title'])) {                $this->fileObject->insertText($line, $formula['col_title'], $formula['title']);            }            if (!isset($formula['col_formula']) || !isset($formula['formula']) || empty($formula['formula'])) {                throw new \Exception('公式格式错误');            }            $formula['formula'] = str_ireplace('{start}', $this->maxHeight + 1, $formula['formula']);            $formula['formula'] = str_ireplace('{end}', $this->maxDataLine, $formula['formula']);            $this->fileObject->insertFormula($line, $formula['col_formula'], $formula['formula']);        }    }        public function reBuildFormulaTop(int $top)    {        $this->defaultFormulaTop = $top;    }        public function setImage(int $row, int $column, string $localImagePath, float $widthScale = 1, float $heightScale = 1)    {        if (!file_exists($localImagePath)) {            throw new \Exception("未检测到图片{$localImagePath}");        }        $this->fileObject->insertImage($row, $column, $localImagePath, $widthScale, $heightScale);    }        public function setFreezeHeader()    {        $this->fileObject->freezePanes($this->maxHeight, 0);    }        public function setFilter()    {        $this->fileObject->autoFilter("A1:{$this->lastColumnCode}");    }        public function setBoldHeader()    {        $this->boldIStyle = $this->format->bold()->toResource();        $this->fileObject->setRow("A1:{$this->lastColumnCode}", $this->defaultHeight, $this->boldIStyle);    }        public function setItalicHeader()    {        $this->boldIStyle = $this->format->italic()->toResource();        $this->fileObject->setRow("A1:{$this->lastColumnCode}", $this->defaultHeight, $this->boldIStyle);    }    public function setAlignCenterHeader()    {        $this->boldIStyle = $this->format->align(\Vtiful\Kernel\Format::FORMAT_ALIGN_CENTER, \Vtiful\Kernel\Format::FORMAT_ALIGN_VERTICAL_CENTER)->toResource();    }    public function setFileProtection($password = null)    {        $this->fileObject->protection($password);    }        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;            // 单元格格式            $format = $this->allowCellFormat[$this->defaultCellFormat];            if (!empty($head['format'])) {                if (!isset($this->allowCellFormat[$head['format']])) {                    throw new \Exception("不支持的单元格格式{$head['format']}");                }                $format = $this->allowCellFormat[$head['format']];            }            // 组装单元格需要的各种信息            $colManage[$column] = [                'title'      => $head['title'],      // 标题                'cursor'     => $cursor,             // 游标                'cursorEnd'  => $cursor,             // 结束游标                'height'     => $col,                // 高度                'width'      => $this->defaultWidth, // 宽度                'format'     => $format,             // 单元格格式                'mergeStart' => $column,             // 合并开始标识                'hMergeEnd'  => $column,             // 横向合并结束标识                'zMergeEnd'  => $column,             // 纵向合并结束标识                'parent'     => $parent,             // 父级标识                'parentList' => $parentList,         // 父级集合            ];            if (!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()    {        foreach ($this->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;            }            // 设置单元格格式            $formatCell =  (new \Vtiful\Kernel\Format($this->fileObject->getHandle()));            $boldStyle  = $formatCell->number($value['format'])->toResource();            // 设置列单元格样式            $toColumnStart = self::getColumn($value['cursor']);            $toColumnEnd   = self::getColumn($value['cursorEnd']);            $this->fileObject->setColumn("{$toColumnStart}:{$toColumnEnd}", $value['width'], $boldStyle);        }    }        private function queryCellActs()    {        if (!empty($this->cellActs)) {            foreach ($this->cellActs as $actNote) {                $tmpActStyle = (new \Vtiful\Kernel\Format($this->fileObject->getHandle()));                // 背景颜色                if (isset($actNote[self::CELL_ACT_BACKGROUND])) {                    foreach ($actNote[self::CELL_ACT_BACKGROUND] as $item) {                        // 支持颜色常量                        $tmpActStyle->background($this->backgroundConst($item['color']));                        $this->fileObject->insertText($item['row'] + $this->maxHeight, $item['column'], $item['val'], '', $tmpActStyle->toResource());                    }                }                // 数据合并                if (isset($actNote[self::CELL_ACT_MERGE])) {                    if (!empty($actNote[self::CELL_ACT_MERGE][self::ACT_MERGE_START]) && !empty($actNote[self::CELL_ACT_MERGE][self::ACT_MERGE_END])) {                        // 合并样式:水平左对齐,垂直居中对齐                        $tmpActStyle->align(\Vtiful\Kernel\Format::FORMAT_ALIGN_LEFT, \Vtiful\Kernel\Format::FORMAT_ALIGN_VERTICAL_CENTER);                        $this->fileObject->mergeCells("{$actNote[self::CELL_ACT_MERGE][self::ACT_MERGE_START]}:{$actNote[self::CELL_ACT_MERGE][self::ACT_MERGE_END]}",$actNote[self::CELL_ACT_MERGE]['val'],$tmpActStyle->toResource()                        );                    }                }            }            $this->cellActs = [];        }    }       private function backgroundConst($color)    {        $const = [            'black'   => \Vtiful\Kernel\Format::COLOR_BLACK,   // 黑色            'blue'    => \Vtiful\Kernel\Format::COLOR_BLUE,    // 蓝色            'brown'   => \Vtiful\Kernel\Format::COLOR_BROWN,   // 棕色            'cyan'    => \Vtiful\Kernel\Format::COLOR_CYAN,    // 青色            'gray'    => \Vtiful\Kernel\Format::COLOR_GRAY,    // 灰色            'green'   => \Vtiful\Kernel\Format::COLOR_GREEN,   // 绿色            'lime'    => \Vtiful\Kernel\Format::COLOR_LIME,    // 石灰            'magenta' => \Vtiful\Kernel\Format::COLOR_MAGENTA, // 洋红            'navy'    => \Vtiful\Kernel\Format::COLOR_NAVY,    // 深蓝            'orange'  => \Vtiful\Kernel\Format::COLOR_ORANGE,  // 橙色            'pink'    => \Vtiful\Kernel\Format::COLOR_PINK,    // 粉红            'purple'  => \Vtiful\Kernel\Format::COLOR_PURPLE,  // 紫色            'red'     => \Vtiful\Kernel\Format::COLOR_RED,     // 红色            'silver'  => \Vtiful\Kernel\Format::COLOR_SILVER,  // 银色            'white'   => \Vtiful\Kernel\Format::COLOR_WHITE,   // 白色            'yellow'  => \Vtiful\Kernel\Format::COLOR_YELLOW,  // 黄色        ];        return $const[$color] ?? $color;    }        private function getColumn($num)    {        return PHPExcel_Cell::stringFromColumnIndex($num);    }}

使用示例

header头规则 title表示列标题,children表示子列,没有子列children可不写或为空

$header = [            [                'title' => '一级表头1',                'children' => [                    [                        'title' => '二级表头1',                    ],                    [                        'title' => '二级表头2',                    ],                    [                        'title' => '二级表头3',                    ],                ]            ],            [                'title' => '公式测试',            ],            [                'title' => '一级表头3',                'children' => [                    [                        'title' => '二级表头1',                        'children' => [[    'title' => '三级表头1',],[    'title' => '三级表头2',],                        ]                    ],                    [                        'title' => '公式测试',                    ],                    [                        'title' => '二级表头3',                        'children' => [[    'title' => '三级表头1',    'children' => [        [            'title' => '四级表头1',            'children' => [                [                    'title' => '五级表头1',                ],                [                    'title' => '五级表头2',                ]            ]        ],        [            'title' => '四级表头2'        ]    ]],[    'title' => '三级表头2',],                        ]                    ]                ]            ],            [                'title' => '一级表头4',                'format' => 'text',            ],            [                'title' => '一级表头5',                'format' => 'text',            ],        ];        $data = [];        for ($i = 0; $i < 35; $i++) {            // 数据合并            $merge = '这是第'. $i .'行测试';            if ($i == 10) {                $merge = [                    '数据合并测试',                    ['merge' => 'start']                ];            }            if ($i == 30) {                $merge = [                    '数据合并测试',                    ['merge' => 'end']                ];            }            // 数据合并+背景颜色            $megBack = '这是第'. $i .'行测试';            if ($i == 0) {                $megBack = [                    '数据合并+背景颜色测试',                    ['merge' => 'start']                ];            }            if ($i == 20) {                $megBack = [                    '数据合并+背景颜色',                    ['merge' => 'end', 'background' => 'brown']                ];            }            // 单个单元格背景颜色            $testBack = '这是第'. $i .'行测试';            if ($i == 10) {                $testBack = [                    '单行单列背景颜色测试',                    ['background' => 'orange']                ];            }            // RGB十六进制颜色            $rgbColor = '这是第'. $i .'行测试';            if ($i == 15) {                $rgbColor = [                    'RGB十六进制颜色测试',                    ['background' => 0xFDB8]                ];            }            $data[] = [                $merge,                '这是第'. $i .'行测试',                $megBack,                rand(1, 99),                $testBack,                $rgbColor,                rand(1, 10),                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',            ];        }        // 公式测试,计算列的总和、平均        $formulas_test = [            [                'col_title' => 2,                'title' => '总计',                'col_formula' => 3,                'formula' => '=SUM(D{start}:D{end})'            ],            [                'col_title' => 5,                'title' => '平均',                'col_formula' => 6,                'formula' => '=AVERAGE(G{start}:G{end})'            ],        ];        // 公式测试,计算列相乘        $formulas_test_two = [            [                'col_title' => 5,                'title' => '总计',                'col_formula' => 6,                'formula' => '=SUM(G{start}:G{end})'            ]        ];        $data_two = [];        for ($i = 0; $i < 30; $i++) {            // 数据合并            $merge = '这是第'. $i .'行测试';            if ($i == 5) {                $merge = [                    '数据合并测试',                    ['merge' => 'start']                ];            }            if ($i == 10) {                $merge = [                    '数据合并测试',                    ['merge' => 'end']                ];            }            // 数据合并+背景颜色            $megBack = '这是第'. $i .'行测试';            if ($i == 0) {                $megBack = [                    '数据合并+背景颜色测试',                    ['merge' => 'start']                ];            }            if ($i == 10) {                $megBack = [                    '数据合并+背景颜色',                    ['merge' => 'end', 'background' => 'brown']                ];            }            // RGB十六进制颜色            $rgbColor = '这是第'. $i .'行测试';            if ($i == 15) {                $rgbColor = [                    'RGB十六进制颜色测试2',                    ['background' => 0xFDB8]                ];            }            $data_two[] = [                $merge,                '这是第'. $i .'行测试',                $megBack,                'test',                'test2',                $rgbColor,                'test',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                $merge,                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                $megBack,            ];        }        $data_three = [];        for ($i = 0; $i < 100; $i++) {            $data_three[] = [                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',                '这是第'. $i .'行测试',            ];        }        try {            $fileName = '很厉害的文件导出类';            $xlsWriterServer = new MultiFloorXlsWriterService();            $xlsWriterServer->setFileName($fileName, '这是Sheet1别名');            $xlsWriterServer->setHeader($header);            $xlsWriterServer->setBoldHeader(); // 设置表头加粗            $xlsWriterServer->setItalicHeader(); // 设置表头斜体            $xlsWriterServer->setFilter(); // 表头开启过滤选项            $xlsWriterServer->setFreezeHeader(); // 冻结表头            $xlsWriterServer->setAlignCenterHeader(); // 设置表头水平居中            $xlsWriterServer->setFileProtection('testpwd'); // 设置文件解除锁定保护密码            $xlsWriterServer->setData($data);            $xlsWriterServer->setFormula($formulas_test); // 设置公式            $xlsWriterServer->reBuildFormulaTop(3);  // 设置公式行距离数据行的间隔(默认2),这里使第二个公式数组在第一个公式下面            $xlsWriterServer->setFormula($formulas_test_two);            $xlsWriterServer->addSheet('这是Sheet2别名');            $xlsWriterServer->setHeader($header); //这里可以使用新的header            $xlsWriterServer->setFreezeHeader(); // 冻结表头            $xlsWriterServer->setData($data_two); // 这里也可以根据新的header定义数据格式            $xlsWriterServer->addSheet('这是Sheet3别名');            $xlsWriterServer->setHeader($header); //这里可以使用新的header            $xlsWriterServer->setFreezeHeader(); // 冻结表头            $xlsWriterServer->setData($data_three); // 这里也可以根据新的header定义数据格式            $filePath = $xlsWriterServer->output(); // 保存到服务器            $xlsWriterServer->excelDownload($filePath); // 输出到浏览器        } catch (\Exception $e) {            exit($e->getMessage());        }

导出效果图:

原:PHP基于xlswriter支持无限表头层级Excel导出_php xlswriter_叶先生i的博客-CSDN博客

来源地址:https://blog.csdn.net/dreamboycs/article/details/130886639

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯