logo听风亭

如风在野,山峦共听

php-Spreadsheet excel表格封装

<?php

namespace app\models\Excel;

use app\models\AliyunOssApi;
use app\models\Util;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use yii\db\ActiveRecord;

/**
 * 基础导表类
 */
class BaseExcel extends ActiveRecord
{
    /**
     * 表格每列展示的字段名称,及对应读取的数据变量
     * 示例
     * array[
     * 'A'=>[
     *      'name'='字段名',
     *      'field'='对应的数据变量名',
     *      'value'='', 数据[field] 为空时 读取
     *      'default'='', field = '' 时 读取
     *  ]
     * ]
     * @var array
     */
    private array $field_list = []; //字段名称数组
    private string $file_name = ''; //文件名
    private string $title_name = '未命名'; //文件表格title名
    private int $column_width = 25;// 列宽
    private array $column_width_fields = [];// 单独设置列宽
    private bool $is_set_border= true;//是否设置边框
    private array $column_content_format= [];//设置列内容格式

    private int $data_start_line = 2; //加载数据开始行数
    private int $name_line = 1; //数据名称展示在哪一行
    private array $configure_top_data = []; //定制头部内容展示
    private array $configure_bottom_data = [];// 定制底部内容展示

    public function __construct()
    {
        $this->file_name = date('YmdHis') .'-'. rand(10000, 99999);
    }

    /**
     * 设置表格导出列,
     * 及每列第一行的字段名称,以及字段对应的变量名
     * @param array $field_list
     * @return $this
     * @throws \app\models\Exception
     */
    public function setFieldlist(array $field_list = []): BaseExcel
    {
        /**
         * $field_list 为数组格式 每个参数的定义
         * name 每列第一行展示的字段名称
         * field 每列对应的变量参数
         * value 当 field参数 对应的变量的值为空时,默认读取 value 的值
         * default 当每列对应得到 field为空时 ,默认读取 default的值
         */
        if (empty($field_list)) {
            throw new \app\models\Exception('SERVER' ,'表格字段名称,及对应展示变量必传');
        }
        $this->field_list = $field_list;
        return $this;

    }

    /**
     * 设置文件名称
     * @param string $file_name
     * @return $this
     */
    public function setFileName(string $file_name = ''): BaseExcel
    {
        $this->file_name = $file_name ?: date('YmdHis') .'-'.rand(10000,99999);
        return $this;
    }

    /**
     * 设置表格名称
     * @param string $title_name
     * @return $this
     */
    public function setTitleName(string $title_name = ''): BaseExcel
    {
        $this->title_name = $title_name ?: '表格内容列表';
        return $this;
    }

    /**
     * 设置统一列宽 默认 25
     * @param int $column_width
     * @return BaseExcel
     */
    public function setColumnWidth(int $column_width = 0): BaseExcel
    {
        $this->column_width = $column_width > 0 ? $column_width : 25;
        return $this;
    }

    /**
     * 导出表格是否需要边框 默认 是
     * @param bool $is_set_border
     * @return BaseExcel
     */
    public function setIsSetBorder(bool $is_set_border = true): BaseExcel
    {
        $this->is_set_border = $is_set_border;
        return $this;
    }

    /**
     * 设置字段内容展示格式
     * @param string $set_column_content_format
     * @param array $set_column_content_format_fields
     * @return $this
     * @throws \app\models\Exception
     */
    public function setColumnContentFormat(string $set_column_content_format = '' ,
        array $set_column_content_format_fields = []): BaseExcel
    {
        if(empty($set_column_content_format)){
            return $this;
        }
        if(empty($set_column_content_format_fields) || !is_array($set_column_content_format_fields)){
            return $this;
        }
        if(empty($this->field_list)){
            throw new \app\models\Exception('SERVER','请先设置基础列字段。');
        }
        $fields_keys = array_keys($this->field_list);
        $differences = array_diff($set_column_content_format_fields, $fields_keys);
        if($differences){
            throw new \app\models\Exception('SERVER','设置样式字段不在基础字段当中');
        }
        switch ($set_column_content_format) {
            case 'FORMAT_NUMBER_00':
                //设置字段内容格式为数字,并保留两位小数
                $style = [
                    'numberFormat' => [
                        'formatCode' => NumberFormat::FORMAT_NUMBER_00,
                    ],
                ];
                $this->column_content_format[] = [
                    'style' => $style,
                    'fields'=> $set_column_content_format_fields,
                ];
                break;
            case 'FORMAT_NUMBER':
                //设置列字段内容为纯数字,主要用来避免超长数字时被序列化问题
                $style = [
                    'numberFormat' => [
                        'formatCode' => NumberFormat::FORMAT_NUMBER,
                    ],
                ];
                $this->column_content_format[] = [
                    'style' => $style,
                    'fields'=> $set_column_content_format_fields,
                ];
                break;

        }

        return $this;
    }

    /**
     * 设置字段内容展示格式
     * @param int $width_value 列宽数值
     * @param array $column_fields 列宽对应列
     * @return $this
     * @throws \app\models\Exception
     */
    public function setColumnWidthFields(int $width_value = 25 ,
        array $column_fields = []): BaseExcel
    {
        if(empty($width_value)){
            return $this;
        }
        if(empty($column_fields) || !is_array($column_fields)){
            return $this;
        }
        if(empty($this->field_list)){
            throw new \app\models\Exception('SERVER','请先设置基础列字段。');
        }
        $fields_keys = array_keys($this->field_list);
        $differences = array_diff($column_fields, $fields_keys);
        if($differences){
            throw new \app\models\Exception('SERVER','设置列宽字段不在基础字段当中');
        }
        $this->column_width_fields[]=[
            'value' => $width_value,
            'fields' => $column_fields,
        ];
        return $this;
    }

    /**
     * 设置展示数据的开始行数 默认 2
     * @param int $start_line
     * @return $this
     */
//    public function setDataStartLine(int $start_line = 2): BaseExcel
//    {
//        if(empty($start_line)){
//            return $this;
//        }
//        $this->name_line = $start_line;
//        return $this;
//    }

    /**
     * 设置表格字段名称所在行数 默认 1
     * @param int $start_line
     * @return $this
     */[object Object]
//    public function setNameLine(int $start_line = 1): BaseExcel
//    {
//        if(empty($start_line)){
//            return $this;
//        }
//        $this->name_line = $start_line;
//        return $this;
//    }

    /**
     * 自定义头部字段内容显示
     * @param array $configure_top_data
     *              'column' 展示自定义内容的单元格
     *              'line' 内容展示在单元格的哪一行
     *              'text' 单元格展示内容
     *              'merge_fields' array 需要合并的单元格,默认为所有列,参数为数组 值为列标识[A,B,C,...]
     * @return $this
     * @throws \app\models\Exception
     */
    public function setConfigureTopData(array $configure_top_data = []): BaseExcel
    {
        if(empty($configure_top_data)){
            return $this;
        }
        if(!empty($configure_top_data['merge_fields'])){
            if(!is_array($configure_top_data['merge_fields'])){
                throw new \app\models\Exception('SERVER','merge_fields 参数必须为数组');
            }
            if(!in_array($configure_top_data['column'], $configure_top_data['merge_fields'])){
                throw new \app\models\Exception('SERVER','column 参数的值,必须在merge_fields内。');
            }
        }
        $this->configure_top_data[] = $configure_top_data;
        $this ->data_start_line += 1;
        $this ->name_line += 1;
        return $this;
    }

    /**
     * 自定义底部字段内容显示
     * @param array $configure_bottom_data
     *              'column' 展示自定义内容的单元格
     *              'line' 内容展示在单元格的哪一行及内容展示结束后的第几行
     *              'text' 单元格展示内容
     *              'merge_fields' array 需要合并的单元格,默认为所有列,参数为数组 值为列标识[A,B,C,...]
     * @return $this
     * @throws \app\models\Exception
     */
    public function setConfigureBottomData(array $configure_bottom_data = []): BaseExcel
    {
        if(empty($configure_bottom_data)){
            return $this;
        }
        if(!empty($configure_bottom_data['merge_fields'])){
            if(!is_array($configure_bottom_data['merge_fields'])){
                throw new \app\models\Exception('SERVER','merge_fields 参数必须为数组');
            }
            if(!in_array($configure_bottom_data['column'], $configure_bottom_data['merge_fields'])){
                throw new \app\models\Exception('SERVER','column 参数的值,必须在merge_fields内。');
            }
        }

        $this->configure_bottom_data[] = $configure_bottom_data;
        return $this;
    }


    /**
     * 基础表格导出操作
     * @param $list
     * @return array
     * @throws Exception
     * @throws \app\models\Exception
     */
    public function getExcel($list): array
    {
        if(empty($this->field_list) || empty($list)){
            throw new \app\models\Exception('SERVER','导表必须设置字段基础信息,并且数据内容不能为空');
        }
        $fileName = $this ->title_name;
        $spreadsheet = new Spreadsheet();
        $spreadsheet->getActiveSheet()->setTitle($fileName);
        $sheet = $spreadsheet->getActiveSheet();
        //定义表格格式
        $excel_row_data = $this ->field_list;
        //获取全部
        $letterList = $this->generateLetters("A", count($excel_row_data));

        $styleArray = [
            'alignment' => [
                'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT,
            ],
        ];
        foreach ($letterList as $v) {
            //设置单元格宽带
            $spreadsheet->getActiveSheet()->getColumnDimension($v)->setWidth($this->column_width);
            //设置单元格内容居左显示
            $sheet->getStyle($v)->applyFromArray($styleArray);
            //设置单元格第一列标题内容
            $sheet->setCellValue($v.$this->name_line, $excel_row_data[$v]['name']);
        }

        //单独设置列表字段
        if($this ->column_width_fields){
            $column_width_fields = $this ->column_width_fields;
            foreach ($column_width_fields  as $v){
                foreach ($v['fields'] as $vv){
                    //设置单元格宽带
                    $spreadsheet->getActiveSheet()->getColumnDimension($vv)->setWidth($v['value']);
                }
            }
        }
        //定制头部内容展示
        if(!empty($this ->configure_top_data)){
            foreach ($this ->configure_top_data as $v){
                //设置单元格内容
                $sheet->setCellValue($v['column'] . $v['line'], $v['text'] );
                //设置合并单元格,默认所有列合并成一个单元格
                if(!empty($v['merge_fields']) && is_array($v['merge_fields'])){
                    sort($v['merge_fields']);
                    $sheet->mergeCells($v['merge_fields'][0] . $v['line'].':'.end($v['merge_fields']).$v['line']);
                }else{
                    $endLetter = end($letterList);
                    $sheet->mergeCells($v['column'] . $v['line'].':'.$endLetter.$v['line']);
                }
            }
        }

        $count = count($list);
        //设置列内容格式
        if(!empty($this ->column_content_format)){
            foreach ($this ->column_content_format as $v){
                foreach ($v['fields'] as $k=>$vv){
                    $sheet->getStyle($vv.$this ->data_start_line.':'.$vv.($count+$this ->data_start_line-1))->applyFromArray($v['style']);
                }
            }
        }
        $i = $this ->data_start_line;
        //表格赋值
        foreach ($list as $k => $v) {
            foreach ($excel_row_data as $k1 => $v1) {
                $value = ($v1['field'] != '' ? $v[$v1['field']] : $v1['default']) ?:$v1['value'];

                if(strpos($value, '==') !== false){
//                    $value = '\''.$value;
                    $sheet->setCellValueExplicit($k1 . ($i),$value ,DataType::TYPE_STRING);
                }else{
                    $sheet->setCellValue($k1 . ($i),$value );

                }
            }
            $i ++ ;

        }
        $end_line = $i-1;
        //定制底部内容展示
        if(!empty($this ->configure_bottom_data)){
            foreach ($this ->configure_bottom_data as $v){
                $current_line = $i+$v['line']-1;
                $end_line = $current_line; // 同时标记表格最后一行
                //设置单元格内容
                $sheet->setCellValue($v['column'] . $current_line, $v['text'] );
                //设置合并单元格,默认所有列合并成一个单元格
                if(!empty($v['merge_fields']) && is_array($v['merge_fields'])){
                    sort($v['merge_fields']);
                    $sheet->mergeCells($v['merge_fields'][0] . $current_line.':'.end($v['merge_fields']).$current_line);
                }else{
                    $endLetter = end($letterList);
                    $sheet->mergeCells($v['column'] . $current_line.':'.$endLetter.$current_line);
                }
            }
        }

        if($this ->is_set_border){
            //表格边框
            $styleArray1 = [
                'borders' => [
                    'allBorders' => [
                        'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN //细边框
                    ]
                ]
            ];
            $endLetter = end($letterList);
            $sheet->getStyle('A1' . ':'.$endLetter . $end_line)->applyFromArray($styleArray1);
        }
        $file_name =  $this ->file_name. '.xlsx';
        $oss_path = 'ticket_order_export_excel/' . date('y/m/');
        $dir_path = 'uploads/' . $file_name;

       $writer = new Xlsx($spreadsheet);
       $writer->save($dir_path); //保存文件
        return ['url' => $dir_path ];

    }
    /**
         * 获取字母数组
         *
         * @param string $start
         * @param int $count
         * @return array
         */
        public function generateLetters(string $start, int $count): array {
            $startCode = ord(strtoupper($start)); // 转成 ASCII 码
            $letters = [];
        
            for ($i = 0; $i < $count; $i++) {
                $letters[] = chr($startCode + $i);
            }
        
            return $letters;
        }

}


Comments 0

0.184079s