[TOC] ### 1、composer 安装phpoffice/phpexcel ~~~ composer require phpoffice/phpexcel ~~~ ### 2、通用建议放在common模块 ~~~ php think make:controller common/Export --plain --plain 表示仅仅生成控制器 ~~~ ### 3、该控制器设置了三种通用导出设置 ~~~ 1、common_excel_export 单页excel导出--不计数量-支持多个sheet工作区 2、caculate_excel_export 多页excel导出--计数量-将相同数据按每个工作区的量导入多个工作区即设置每个工作的上限 3、recursion_create_excel 导出excel--支持生成表头-插入数据-设置数据等功能 ~~~ ### 4、开始代码--入口统一 ~~~ /** * 导出excel入口 * * @param integer $type 选择导出的类型 * @param array $data 需要处理的数据 * @return void */ public static function export($type, array $data = []) { switch ($type) { case 1: //单页excel导出--不计数量--可以导出多个sheet工作区 $fun = 'common_excel_export'; break; case 2: //多页excel导出--计数量--将相同数据按每个工作区的量导入多个工作区即设置每个工作的上限 $fun = 'caculate_excel_export'; break; case 3: //导出excel 生成表头 插入数据,设置数据等功能 $fun = 'recursion_create_excel'; break; default: break; } self::$fun($data); } 解释说明:通过php可变函数特性处理,data数据在具体方法中解析说明 ~~~ ### 5、common_excel_export 方法实现 ~~~ 代码说明在代码中解析说明 /** * 单页导出--不计数量--可以导出多个sheet工作区 * * @param array $data 需要的相关数据 * $data = [ * //表示sheet0对应需要导出的数据 数据是否存在根据key中的keys个数判断 * 'sheet0'=>['data' => $dataInfos], * //表示sheet1对应需要导出的数据 * 'sheet1'=>['data' => $dataInfos2], * //excel中对应的key * 'key' => 'common_export', * //导出的文件名 * 'fileInfo' => [ * //文件名 * 'fileName' => '多页导出计数测试', * //工作表名替换key中设置的,不设置将使用key中设置的 * 'sheetName0' => '测试替换', * 'sheetName1' => '配置不设置' * ] * ]; * * @return void */ protected static function common_excel_export(array $data) { @ini_set('memory_limit', '2048M'); set_time_limit(0); error_reporting(E_ALL); //在data中必须指定excel【config/excel.php】配置文件中的key //key的具体信息将在后面给出 $key = $data['key']; //fileInfo数组中配置一些简单的excel文件信息 $fileInfo = $data['fileInfo']; //fileInfo中的fileName将会覆盖配置文件中的fileName $fileName = $fileInfo['fileName']; //获取excel配置文件中的数据,具体有哪些稍后说明 $excelInfo = Config::get('excel.' . $key); //判断文件名 $fileName = empty($fileName) ? ($excelInfo['fileName'] ?? uniqid().time()) : $fileName; unset($data['key']); unset($data['fileInfo']); ksort($data); $objPHPExcel = \PHPExcel_IOFactory::createReader('Excel5')->load($excelInfo['tempExcel']); $index = 0; foreach ($excelInfo['keys'] as $v) { $objPHPExcel->getSheet($v['index']) ->setTitle($fileInfo['sheetName'.$v['index']] ?? $v['sheetName']); $dataInfos = $data['sheet' . $index]['data']; $num = $v['num']; $keys = $v['key']; foreach ($dataInfos as $info) { $column = count($keys); $temp = 0; for ($n = 0; $n < $column; $n++) { if ($temp == $column) { break; } else { $pcoordinate = \PHPExcel_Cell::stringFromColumnIndex($n) . '' . $num; $keys[$temp] == 'index_id' ? ( $objPHPExcel->setActiveSheetIndex($v['index']) ->setCellValue($pcoordinate, ($num-1)) ) : ( $objPHPExcel->setActiveSheetIndex($v['index']) ->setCellValue($pcoordinate, $info[$keys[$temp]] . "\t") ); $temp++; } } $num++; } $index++; } ob_end_clean(); $fileName = iconv("utf-8", "gb2312", $fileName); header ( 'Content-Type: application/vnd.ms-excel' ); header ( 'Content-Disposition: attachment;filename="' . $fileName . '.xls"'); header ( 'Cache-Control: max-age=0' ); $objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5'); $objWriter->save('php://output'); exit; } ~~~ config目录下的excel配置文件说明 ~~~ //key值唯一 'common_export' => [ //需要加载的模板文件 'tempExcel' => './mdfile/excels/common_export.xls', //文件名 'fileName' => '测试通用', //相关数据配置 'keys' => [ [ //与数据对应的字段,字段顺序对应模板中的显示字段,其中id代表序号(不需要数据库中需要) 'key'=>[ 'id','name','idcard','gender','age','birthday','phone','address' ], //表示从第几行开始写入【excel行的下标计算从1开始】 'num' => 2, //对应excel中的工作区,且需要与调用方法中的sheet0,1,2对应 'index' => 0, //当前工作区的名称 'sheetName' => '测试不替换', ], [ //与数据对应的字段,字段顺序对应模板中的显示字段,其中id代表序号(不需要数据库中需要) 'key'=>[ 'id','name','idcard','gender','age','birthday','phone','address' ], //表示从第几行开始写入【excel行的下标计算从1开始】 'num' => 2, //对应excel中的工作区,且需要与调用方法中的sheet0,1,2对应 'index' => 1, ], ], ], ~~~ ### 6、caculate_excel_export ~~~ /** * 多页导出--计数量--将相同数据按每个工作区的量导入多个工作区即设置每个工作的上限---只支持无表头 * * @param array $data 需要的相关数据 *$data = [ * //表示sheet0对应需要导出的数据 数据是否存在根据key中的keys个数判断 * 'sheet0'=>['data' => $dataInfos], * //表示sheet1对应需要导出的数据 * 'sheet1'=>['data' => $dataInfos2], * //excel中对应的key * 'key' => 'caculate_export', * //导出的文件名 * 'fileInfo' => [ * //文件名 * 'fileName' => '多页导出计数测试', * //工作表名替换key中设置的,不设置将使用key中设置的 * 'sheetName0' => '测试替换', * 'sheetName1' => '配置不设置' * ] * ]; * * @return void */ protected static function caculate_excel_export(array $data) { @ini_set('memory_limit', '2048M'); set_time_limit(0); error_reporting(E_ALL); $key = $data['key']; $fileInfo = $data['fileInfo']; $fileName = $fileInfo['fileName']; $excelInfo = Config::get('excel.' . $key); $fileName = empty($fileName) ? ($excelInfo['fileName'] ?? uniqid().time()) : $fileName; unset($data['key']); unset($data['fileInfo']); ksort($data); $objPHPExcel = \PHPExcel_IOFactory::createReader('Excel5')->load($excelInfo['tempExcel']); $sheetindex = 0; $temp_sheet_index = 0; $sheetSize = 40000; foreach ($excelInfo['keys'] as $v) { $dataIndex = $v['index']; $dataInfos = $data['sheet' . $dataIndex]['data']; $num = $v['num']; $keys = $v['key']; $row_size = (isset($v['sheetSize']) && is_numeric($v['sheetSize'])) ? intval($v['sheetSize']) : $sheetSize; $temp_s = 0; $data_len = count($dataInfos); $sheet_counts = ceil(bcdiv($data_len, $row_size, 6)); $sheet_temp = 0; $objPHPExcel->getSheet($sheetindex) ->setTitle(($fileInfo['sheetName'.$dataIndex] ?? $v['sheetName']) . $sheet_temp ); foreach ($dataInfos as $info) { $column = count($keys); $temp = 0; for ($n = 0; $n < $column; $n++) { if ($temp == $column) { break; } else { $pcoordinate = \PHPExcel_Cell::stringFromColumnIndex($n) . '' . $num; $keys[$temp] == 'index_id' ? ( $objPHPExcel->setActiveSheetIndex($sheetindex) ->setCellValue($pcoordinate, $num) ) : ( $objPHPExcel->setActiveSheetIndex($sheetindex) ->setCellValue($pcoordinate, $info[$keys[$temp]] . "\t") ); $temp++; } } $num++; $temp_s ++; if ($temp_s % $row_size == 0 && $temp_s <= $data_len) { $objPHPExcel->createSheet(); $sheetindex++; $sheet_temp++; if ($sheet_temp < $sheet_counts) { $sheetName = $fileInfo['sheetName' . $dataIndex] ?? $v['sheetName']; $objPHPExcel->getSheet($sheetindex) ->setTitle($sheetName . $sheet_temp); } $num = $v['num']; } } } ob_end_clean(); $fileName = iconv("utf-8", "gb2312", $fileName); header ( 'Content-Type: application/vnd.ms-excel' ); header ( 'Content-Disposition: attachment;filename="' . $fileName . '.xls"'); header ( 'Cache-Control: max-age=0' ); $objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5'); $objWriter->save ('php://output'); exit; } ~~~ ~~~ 'caculate_export' => [ //需要加载的模板文件 'tempExcel' => './mdfile/excels/common_export.xls', //文件名 'fileName' => '测试分页', //相关数据配置 'keys' => [ [ //与数据对应的字段,字段顺序对应模板中的显示字段,其中id代表序号(不需要数据库中需要) 'key'=>[ 'index_id','name','idcard','gender','age','birthday','phone','address' ], //表示从第几行开始写入【excel行的下标计算从1开始】 'num' => 1, //对应数据序列 'index' => 0, //sheetSize设置每个工作区的大小 'sheetSize' => 2000, //当前工作区的名称 'sheetName' => '测试不替换', ], [ //与数据对应的字段,字段顺序对应模板中的显示字段,其中id代表序号(不需要数据库中需要) 'key'=>[ 'index_id','name','idcard','gender','age','birthday','phone','address' ], //表示从第几行开始写入【excel行的下标计算从1开始】 'num' => 1, //sheetSize设置每个工作区的大小 'sheetSize' => 2000, //对应数据序列 'index' => 1, ], ], ], ~~~ ### 7、recursion_create_excel ~~~ /** * 生成表头导出数据 * * @param array $data * $data = [ * 'head' => [ * [ * 'value' => '表头名', * 'col' => 2, //占据多少列 * 'row' => 2, //占据多少行 * 'width' => 20, //单元格宽度 * //单元格下拉格式 list=下拉选择,range=范围选择 * 'type' => 'list', * //下拉数据,以英文逗号分隔 * 'allowarray' => 'aa,bb', * 'content' => '备注信息' * ], * [ * 'value' => '表头名', * 'col' => 2, * 'row' => 1, * 'width' => 20, * 'content' => '备注信息', * //下一行数据 * 'children' => [ * [ * 'value' => '表头名', * 'col' => 1, * 'width' => 20, * //单元格范围设置 * 'type' => 'range', * //范围数据,以英文逗号分隔,仅支持最大最小值设置 * 'allowarray' => '10,100' * ], * [ * 'value' => '', * 'width' => 20 * ], * ], * ], * [], * [] * ], * //需要插入的数据 * 'data' => [ * [],[],[],[] * ], * //文件名 * 'fileName' => '', * //从第几行开始插入数据 * 'row' => 2 * ]; * * @return void */ protected static function recursion_create_excel(array $data) { $PHPExecl = new \PHPExcel(); $objWriter = \PHPExcel_IOFactory::createWriter($PHPExecl, 'Excel2007'); $PHPExecl ->getProperties() ->setCreator("4399om") ->setTitle("Office 2007 XLSX Test Document") ->setSubject("Office 2007 XLSX Test Document") ->setDescription("Generate document for Office 2007 XLSX, generated using PHP classes.") ->setKeywords("office 2007 openxml php") ->setCategory("Test result file"); $PHPExecl ->setActiveSheetIndex(0); $PHPExecl ->getActiveSheet() ->getDefaultRowDimension() ->setRowHeight(30); $sheet = $PHPExecl->getActiveSheet(); self::generate_excel_header($sheet, $data['head'], 1, 0, 0); self::summer_insert_data_to_excel( $sheet, $data['head'], $data['data'], $data['row'] ); self::out_input_header($objWriter, $data['fileName']); } /** * 生成表头 * * @param object $sheet * @param array $head 表头数据 * @param integer $beginRow 起始行 * @param integer $col 起始列 * @param integer $startCol 开始列 * @return void */ private static function generate_excel_header( $sheet, $head, $beginRow, $col, $startCol ) { foreach ($head as $key => $cells) { $row = $beginRow; $beginCol = \PHPExcel_Cell::stringFromColumnIndex($col) . $row; $sheet ->getCell($beginCol) ->setValue($cells['value']); //设置表格样式 $sheet ->getStyle($beginCol) ->applyFromArray(Config::get('excel.excel_type_03')); //设置单元格的宽度 if(isset($cells['width'])) { $Cell = $sheet->getColumnDimension(\PHPExcel_Cell::stringFromColumnIndex($col)); $Cell->setWidth($cells['width']); } //元素打上标记 if (isset($cells['content'])) { self::set_comment($sheet, $beginCol, $cells['content']); } //合并单元格 $merge = false; if (isset($cells['col'])) { $col += $cells['col'] - 1; $merge = true; } if (isset($cells['row'])) { $row += $cells['row'] - 1; $merge = true; } if ($merge) { $endCol = \PHPExcel_Cell::stringFromColumnIndex($col) . $row; $sheet->mergeCells($beginCol . ':' . $endCol); } $row ++; $col ++; if (isset($cells['children']) && is_array($cells['children'])) { $cols = $startCol; if (! self::is_exist_chilren($cells['children'])) { $cols = $col - 2; $startCol = $col; } self::generate_excel_header( $sheet, $cells['children'], $row, $cols, $startCol ); } else { $startCol = $col; } } } /** * 判断自己的孩子节点中是否存在孙子节点 * * @param array $data * @return bool */ private static function is_exist_chilren($data) { foreach ($data as $key => $value) { if (isset($value['children']) && is_array($value['children'])) { return true; } } return false; } /** * 生成Execl单元格备注 * * @param object $sheet 当前的工作簿对象 * @param integer $cell 需要设置属性的单元格 * @param string $content 备注内容 * @return void */ private static function set_comment($sheet, $cell, $content) { $sheet->getComment($cell)->setAuthor('4399om'); $objCommentRichText = $sheet->getComment($cell) ->getText() ->createTextRun('4399om:'); $objCommentRichText ->getFont() ->setBold(true); $sheet ->getComment($cell) ->getText() ->createTextRun("\r\n"); $sheet ->getComment($cell) ->getText() ->createTextRun($content); $sheet ->getComment($cell) ->setWidth('100pt'); $sheet ->getComment($cell) ->setHeight('100pt'); $sheet ->getComment($cell) ->setMarginLeft('150pt'); $sheet ->getComment($cell) ->getFillColor() ->setRGB('EEEEEE'); } /** * 将数据写入到数据表中 * * @param object $sheet * @param array $head * @param array $data 要插入进Execl数据 * @param integer $n 表示从第几行起的插入数据 * @param array $ruleData 表示数据格式的规则数组 * @return void */ public static function summer_insert_data_to_excel( $sheet, $head, $data, $n = 3, array $ruleData = [] ) { $simpleHead = self::get_head($head); $row = $n; foreach ($data as $key => $valueArr) { $m = 0; foreach ($valueArr as $k=>$v) { $startCol = \PHPExcel_Cell::stringFromColumnIndex($m) . $row; $sheet ->getCell($startCol) ->setValue($v); $sheet ->getStyle($startCol) ->getAlignment() ->applyFromArray([ 'horizontal'=> \PHPExcel_Style_Alignment::HORIZONTAL_CENTER, 'vertical' => \PHPExcel_Style_Alignment::VERTICAL_CENTER, 'rotation' => 0, 'wrap' => true, ]); if (isset($simpleHead[$k]['col'])) { $m = $m + $simpleHead[$k]['col'] - 1; $endCol = \PHPExcel_Cell::stringFromColumnIndex($m) . $row; $sheet->mergeCells($startCol . ':' . $endCol); } $m++; $type = false; if (isset($simpleHead[$k]['type'])) { $type = $simpleHead[$k]['type']; $allowArray = $simpleHead[$k]['allowarray']; } //设置单元格的数据验证 if ($type) { switch ($type) { case 'list': self::set_selection_range($sheet, $startCol, $allowArray); break; case 'range': self::set_value_range($sheet, $startCol, $allowArray); break; default: break; } } } $row ++; } } /** * 获取底层数据 * * @param array $head * @param array $node * @return array */ private static function get_head($head, &$node = []) { foreach ($head as $key => $value) { if (isset($value['children']) && is_array($value['children'])) { self::get_head($value['children'], $node); } else { $node[] = $value; } } return $node; } /** * 数据控制,设置单元格数据在一个可选方位类 * * @param object $sheet * @param integer $cell * @param string $rangeStr * @param string $title * @return void */ private static function set_selection_range( $sheet, $cell, $rangeStr, $title = '数据类型' ) { $objValidation = $sheet ->getCell($cell) ->getDataValidation(); $objValidation ->setType(\PHPExcel_Cell_DataValidation::TYPE_LIST) ->setErrorStyle(\PHPExcel_Cell_DataValidation::STYLE_STOP) ->setAllowBlank(true) ->setShowInputMessage(true) ->setShowErrorMessage(true) ->setShowDropDown(true) ->setErrorTitle('输入的值有误') ->setError('您输入的值不在下拉框列表内.') ->setPromptTitle('"'.$title.'"') ->setFormula1('"'.$rangeStr.'"'); } /** * 现在单元格的有效数据范围,暂时仅限于数字 * @param object $sheet 当前的工作簿对象 * @param integer $cell 需要设置属性的单元格 * @param string $valueRange 允许输入数组的访问 * @return void */ private static function set_value_range($sheet, $cell, $valueRange) { //设置单元格的的数据类型是数字,并且保留有效位数 $sheet ->getStyle($cell) ->getNumberFormat() ->setFormatCode(\PHPExcel_Style_NumberFormat::FORMAT_NUMBER_00); $valueRange = explode(',', $valueRange); //开始数值有效访问设定 $objValidation = $sheet ->getCell($cell) ->getDataValidation(); $objValidation->setType(\PHPExcel_Cell_DataValidation::TYPE_WHOLE ); $objValidation->setErrorStyle(\PHPExcel_Cell_DataValidation::STYLE_STOP ); $objValidation->setAllowBlank(true); $objValidation->setShowInputMessage(true); $objValidation->setShowErrorMessage(true); $objValidation->setErrorTitle('输入错误'); $objValidation->setError('请输入数据范围在从' . $valueRange[0] . '到' . $valueRange[1] . '之间的所有值'); $objValidation->setPromptTitle('允许输入'); $objValidation->setPrompt('请输入数据范围在从' . $valueRange[0] . '到' . $valueRange[1] . '之间的所有值'); $objValidation->setFormula1($valueRange['0']); $objValidation->setFormula2($valueRange['1']); } /** * 输出 * * @param object $objWriter * @return void */ private static function out_input_header($objWriter, $fileName = '') { ob_end_clean(); $fileName = ($fileName == '' ? time() : $fileName) . '.xlsx'; header("Content-Type: application/force-download"); header("Content-Type: application/octet-stream"); header("Content-Type: application/download"); header('Content-Disposition:inline;filename="'.$fileName.'"'); header("Content-Transfer-Encoding: binary"); header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); header("Pragma: no-cache"); $objWriter->save('php://output'); exit; } ~~~ ### 8、作者有话说 该些封装提供给phper,希望减轻一些不必要的弯路 该些方法没有设置一些文档属性等功能 phper可以根据具体需求做调整