美文网首页
php_xlsxwriter替换php_execl导出excel

php_xlsxwriter替换php_execl导出excel

作者: 李傲强 | 来源:发表于2020-10-14 10:03 被阅读0次

    最近公司业务有一些需要导出多级表头的业务需求,百度之后,没有很好的应用demo,然后接触了XLSXWriter,就想着一劳永逸,写个工具类,示例只有三级,但是不止于三级

    应用举例

    比如说我们需要这样一个表头


    image

    1.XLSXWriter应用分析

    根据XLSXWriter工具类的示例

    我们需要插入三行数据作为表头

    image

    然后再合并掉对应的单元格

    image

    2.工具类编写

    调试的注释啥的,都在里面,有兴趣的自己可以看一下。需要放在和xlsxwriter.class.php同一目录下使用

    <?php
    require_once 'web/libraries/XLSXwriter.php';
    class PHPExeclCore
    {
        private $headerData = array();
        private $headerWidthSize = 0;
        private $headerHeightSize = 0;
    
    
        private $colDefalutFormat = 'string';// 列默认数据格式
        private $colDefalutWidth = '10';// 列默认宽度
    
        private $header = array();
        private $headerWidth = array();
    
        // 数据字段
        private $dataFields = array();
    
        // 标题行默认样式
        private $headerDefalutStyle = array(
            'halign' => 'center',//水平居中
            'valign' => 'center',//竖直居中
            'font-style' => 'bold',
            'font-size' => 12,
            'border' => 'top,bottom,left,right'
            //'fill'=>'#fff'// 背景色
            // 边框有点问题不能用
            // 高度也不行 用font-size撑开);
        );
        // 标题行处理数组
        /**
         * $headerRow = array(
         * array('content' =>['Merge Cells Example','','','',''],'style'=>[['halign'=>'center'],array(),array(),array(),array()]),
         * array('content' =>['姓名', '吃饭', '', '考勤', ''],'style'=>[['halign'=>'center','valign'=>'center'],['halign'=>'center'],[],['halign'=>'center'],[]]),
         * array('content' =>['', '上午', '下午', '上午', '下午'],'style'=>[[],['halign'=>'center'],[],[],[]]),
         * );
         */
        private $headerRow = array(0 => array('content' => array(), 'style' => array()));
        private $headerMargin = array();
    
        // 数据行
        private $dataRow = array();
        // 现在够用了,先不做了
        private $dataStyle = array();
    
    
        public function __construct()
        {
        }
    
        public function __get($name)
        {
            if (isset($this->$name)) {
                return $this->$name;
            } else {
                return null;
            }
            // TODO: Implement __get() method.
        }
    
        /**
         * 设置表头
         * @param array $head
         */
        public function setHeader($header = array())
        {
            $this->headerData = $header;
            // 设置表头尺寸
            $this->setHeaderSize($this->headerData);
            // 生成rows 和 margin 设置样式
            $this->dealHeader($this->headerData);
        }
    
    
        public function setData($data = array())
        {
            foreach ($data as $d) {
                $row = array();
                foreach ($this->dataFields as $field) {
                    $value = isset($d[$field]) ? $d[$field] : '';
                    $row[] = $value;
                }
                $this->dataRow[] = $row;
            }
        }
    
    
        /**
         * 导出
         * @param string $filename
         * @param string $sheet
         */
        public function writeToStdOut($filename = 'test', $sheet = 'Sheet1')
        {
            //$header = ['字段','标题','宽度','类型']
            $filename .= '.xlsx';
            $writer = new \XLSXWriter();
            $writer->writeSheetHeader($sheet, $this->header, $col_options = array('suppress_row' => true, 'widths' => $this->headerWidth));
            foreach ($this->headerRow as $row)
                $writer->writeSheetRow($sheet, $row['content'], $row['style']);
            foreach ($this->headerMargin as $margin) {
                $writer->markMergedCell($sheet, $margin['startRow'], $margin['startCol'], $margin['endRow'], $margin['endCol']);
            }
            foreach ($this->dataRow as $data)
                $writer->writeSheetRow($sheet, $data);
            header('Content-disposition: attachment; filename="' . \XLSXWriter::sanitize_filename($filename));
            header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            header('Content-Transfer-Encoding: binary');
            header('Cache-Control: must-revalidate');
            header('Pragma: public');
            $writer->writeToStdOut();
        }
    
    
        /**
         *  生成rows 和 margin 设置样式
         * @param $headerData
         */
        private function dealHeader($headerData)
        {
    
            $this->recursionSetHeaderRowsAndMargin($headerData, 0, 0, $this->headerDefalutStyle);
        }
    
        /**
         * 递归设置表头行数据,和合并表格数据源
         * @param $headerData
         * @param $row
         * @param $startCol
         * // 换个思路,填坑赋值
         */
        private function recursionSetHeaderRowsAndMargin($headerData, $row, $col, $defStyle = array())
        {
            $heightSize = $this->headerHeightSize;
    
            $startRow = $row;
            $startCol = $col;
            $content = array();//标题
            $style = array();//样式
    
            foreach ($headerData as $single) {
                $marginCol = isset($single['marginCol']) ? $single['marginCol'] : 1;
                // 合并
                // 是否有子节点
                $hasChildren = (isset($single['children']) && !empty($single['children'])) ? true : false;
                // 有子节点合并一行,没有子节点直接把剩下行都算上
                if ($hasChildren) {
                    $marginRow = 1;
                } else {
                    // 一共3行 当前第1行需要合并1,2两行
                    $marginRow = $heightSize - $startRow;
                }
    
    
                $style = isset($single['style']) ? array_merge($defStyle, $single['style']) : $defStyle;
                // 如果不是合并一行一列就加入合并单元格的数组,同时每个合并的单元格都加样式(主要是方便边框)
                if ($marginCol != 1 || $marginRow != 1) {
                    //0+2-1=1 合并了0,1两个单元格 end是1
                    $endCol = $startCol + $marginCol - 1;
                    // 当前第1行需要合并2两行,目标单元格行号1+2-1
                    $endRow = $startRow + $marginRow - 1;
                    $this->headerMargin[] = array(
                        'startRow' => $startRow,
                        'startCol' => $startCol,
                        'endRow' => $endRow,
                        'endCol' => $endCol);
    
                    if (isset($style['border'])) {
                        $s = array('border' => $style['border']);
                        for ($r = 0; $r < $marginRow; $r++) {
                            $row = $startRow + $r;
                            for ($c = 0; $c < $marginCol; $c++) {
                                $col = $startCol + $c;
                                $this->setHeaderRowCell($row, $col, 'style', $s);
                            }
                        }
                    }
    
                }
                $this->setHeaderRowCell($startRow, $startCol, 'style', $style);
    
    
                $content = isset($single['title']) ? $single['title'] : '';
                $this->setHeaderRowCell($startRow, $startCol, 'content', $content);
    
    
                //处理子节点
                if ($hasChildren) {
                    $this->recursionSetHeaderRowsAndMargin($single['children'], $startRow + 1, $startCol, $style);
                }
                $startCol += $marginCol;
    
            }
    
    
        }
    
        //
        //
        /**
         * 设置表头单元格尺寸
         * 同事设置列的数据类型和宽度
         * @param $header
         */
        private function setHeaderSize(&$headerData)
        {
    
            list($w, $h) = $this->recursionCalSize($headerData);
            $this->headerWidthSize = $w;
            $this->headerHeightSize = $h;
            $this->setHeaderRow($w, $h);
    
    
        }
    
        /**
         * 初始化表头行,占坑
         * @param $w
         * @param $h
         */
        private function setHeaderRow($w, $h)
        {
            $data = array();
            $content = array();
            $style = array();
            while (true) {
                $w--;
                $content[] = '';
                $style[] = array();
                if ($w <= 0) break;
            }
    
            while (true) {
                $h--;
                $data[] = array('content' => $content, 'style' => $style);
    
                if ($h <= 0)
                    break;
            }
    
            $this->headerRow = $data;
            unset($content);
            unset($style);
            unset($data);
    
        }
    
        /**
         * 修改表头行的数据
         * @param $col
         * @param $row
         * @param $filed
         * @param $value
         */
        private function setHeaderRowCell($row, $col, $key, $value)
        {
    
            if (isset($this->headerRow[$row][$key][$col])) {
                $this->headerRow[$row][$key][$col] = $value;
            } else {
                return false;
            }
        }
    
        /**
         *
         * @param $headerData
         * @return array
         */
        private function recursionCalSize(&$headerData)
        {
            $w = 0; // 根节点++
            $h = 0; // 是子类高度的最大值
            $childHeightArr = array();
            foreach ($headerData as &$single) {
                $singleH = 1;
                // 没有子节点设置为空数组
    
                if ((isset($single['children']) && !empty($single['children']))) {
                    list($cw, $ch) = $this->recursionCalSize($single['children'], $h);
                    $w += $cw;
                    $singleH += $ch;
                    $single['marginCol'] = $cw;
    
                } else {
    
                    //设置表头需要要参数
                    //字段类型
                    $type = isset($single['format']) ? $single['format'] : $this->colDefalutFormat;
                    // 字段宽度
                    $width = isset($single['width']) ? $single['width'] : $this->colDefalutWidth;
                    $this->header[] = $type;
                    $this->headerWidth[] = $width;
                    // data的键
                    $field = isset($single['field']) ? $single['field'] : '';
                    $this->dataFields[] = $field;
    
                    $w++;
                    $singleH = 1;
                }
                $childHeightArr[] = $singleH;
            }
            $h = max($childHeightArr);
            return array($w, $h);
        }
    }
    

    3.使用示例

    
    <?php
    /**
     * 表头配置为一个数组
     * 根节点参数
     * title  必填,表头的内容
     * field  根节点列的内容要显示的data字段,默认为空[则这一列不显示数据]
     * width  根节点的列宽,默认为10
     * format 根节点的列在excel中的显示类型,默认为string,其他的我没有测试,因为我实际的应用过程中,String就够用了,如果要使用其他类型,请参照xlsxwriter示例自行修改测试
     *      format和width的默认值可以再tool里面修改
     *      colDefalutFormat = 'string';// 列默认数据格式
     *      colDefalutWidth = '10';// 列默认宽度
     *
     * 一般节点的参数
     * title  必填,表头的内容
     * style=> 应用的样式,会应用到根节点
     *      默样式可以修改headerDefalutStyle的配置
     * children 必填,子节点
     */
    $headerConfig =
                    array(
                        array('title' => '存量项目', 'field' => 'project_name','format'=>'string'),//根节点
                        array('title' => '业态', 'field' => 'layout','format'=>'string'),//根节点
                        array('title' => '标签', 'field' => 'project_label','format'=>'string'),//根节点
                        array('title' => '委托管理公司', 'field' => 'business_area_company','format'=>'string','width'=>'20'),//根节点
    //                    ['title' => '姓名', 'field' => 'name','width'=>'40'],//根节点
                        // 一般节点
                        array('title' => '建筑面积',
                            'children' => array(
                                array('title' => '6月30日', 'field' => 'build_area_sum_1'),
                                array('title' => '9月30日', 'field' => 'build_area_sum_2'),
                            )
                        ),
                        array(
                            'title' => '实际收入',
                            'children' => array(
                                array('title' => '6月30日', 'field' => 'rent_income_sum_1'),
                                array('title' => '9月30日', 'field' => 'rent_income_sum_2'),
                            )
                        ),
                        array('title' => '单价',
                            'children' => array(
                                array('title' => '6月30日', 'field' => 'avg_price_1'),
                                array('title' => '9月30日', 'field' => 'avg_price_2'),
                                array('title' => '差值', 'field' => 'avg_price_diff')
                            )
                        ),
                        array('title' => '空置率(%)',
                            'children' => array(
                                array('title' => '6月30日', 'field' => 'rent_rate_1'),
                                array('title' => '9月30日', 'field' => 'rent_rate_2'),
                                array('title' => '差值', 'field' => 'rent_rate_diff')
                            )
                        )
                    );
    $data = [
                ['project_name' => '张三', 'project_name' => '8:23', 'build_area_sum_1' => '9:23', 'avg_price_1' => '9:23'],
                ['project_name' => '李四', 'project_name' => '8:24', 'build_area_sum_1' => '9:24', 'avg_price_1' => '9:24'],
            ];
    
    include_once("../extend/PHP_XLSXWriter-master/XLSXWriterMultiHeaderTool.php");
    $tool = new \XLSXWriterMultiHeaderTool();
    $tool->setHeader($headerConfig);
    $tool->setData($data);
    $filename = date('_YmdHis');
    $tool->writeToStdOut($filename);
    

    4.效果

    因为data里面没有对应的字段,或者节点设置的时候没有指定的field,所以有几列是空的,表头的颜色是因为写了style的fill属性

    image

    相关文章

      网友评论

          本文标题:php_xlsxwriter替换php_execl导出excel

          本文链接:https://www.haomeiwen.com/subject/vluipktx.html