企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] ## **本例以thinkphp5.1为例** 包地址: ~~~ https://packagist.org/packages/phpoffice/phpexcel ~~~ 使用: ~~~ composer require phpoffice/phpexcel ~~~ 控制器引入 ~~~ //引入phpoffice use PHPExcel; use PHPExcel_IOFactory; 导出代码: ~~~ ~~~ //execl模板下载 public function template_download() { $objExcel = new PHPExcel(); $objWriter = \PHPExcel_IOFactory::createWriter($objExcel, 'Excel5'); $objActSheet = $objExcel->getActiveSheet(0); $objActSheet->setTitle('会员批量导入模板'); //设置excel的标题 $objActSheet->setCellValue('A1', '用户id'); $objActSheet->setCellValue('B1', '昵称'); $objActSheet->setCellValue('C1', '手机号'); $baseRow = 2; //数据从N-1行开始往下输出 这里是避免头信息被覆盖 //默认数据 $explame_data = array( array( 'user_id' => '1', 'nickname' => '小明', 'phone' => '15012345678', ), ); foreach ($explame_data as $key => $value) { $i = $baseRow + $key; $objExcel->getActiveSheet()->setCellValue('A' . $i, $value['user_id']); $objExcel->getActiveSheet()->setCellValue('B' . $i, $value['nickname']); $objExcel->getActiveSheet()->setCellValue('C' . $i, $value['phone']); } $objExcel->setActiveSheetIndex(0); //4、输出 $objExcel->setActiveSheetIndex(); header('Content-Type: applicationnd.ms-excel'); $time = date('Y-m-d'); header("Content-Disposition: attachment;filename=会员批量导入模板" . $time . ".xls"); header('Cache-Control: max-age=0'); $objWriter->save('php://output'); } ~~~ 导入代码: ~~~ public function import_batch_send() { header("content-type:text/html;charset=utf-8"); //上传excel文件 $file = request()->file('file'); //将文件保存到public/uploads目录下面 $info = $file->validate(['size' => 1048576, 'ext' => 'xls,xlsx'])->move('./uploads'); if ($info) { //获取上传到后台的文件名 $fileName = $info->getSaveName(); //获取文件路径 $filePath = Env::get('root_path') . 'public' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . $fileName; //获取文件后缀 $suffix = $info->getExtension(); //判断哪种类型 if ($suffix == "xlsx") { $reader = \PHPExcel_IOFactory::createReader('Excel2007'); } else { $reader = PHPExcel_IOFactory::createReader('Excel5'); } } else { return json(['status' => '1', 'message' => '文件过大或格式不正确导致上传失败-_-!']); } //载入excel文件 $excel = $reader->load($filePath, $encode = 'utf-8'); //读取第一张表 $sheet = $excel->getSheet(0); //获取总行数 $row_num = $sheet->getHighestRow(); //获取总列数 $col_num = $sheet->getHighestColumn(); $import_data = []; //数组形式获取表格数据 for ($i = 2; $i <= $row_num; $i++) { $import_data[$i]['nickname'] = $sheet->getCell("B" . $i)->getValue(); $import_data[$i]['phone'] = $sheet->getCell("C" . $i)->getValue(); } if (empty($import_data)) { return json(['status' => '1', 'message' => '数据解析失败']); } //校验手机号是否重复 $phone_array = array_column($import_data, 'phone'); $phone_ids = implode(',', $phone_array); $result_phone = db('user') ->field('phone') ->where('phone', 'in', $phone_ids) ->select(); if (!empty($result_phone)) { $result_phone_array = array_column($result_phone, 'phone'); $result_phone_ids = implode(',', $result_phone_array); return json(['status' => '3', 'message' => '数据重复', 'result' => $result_phone_ids]); } //将数据保存到数据库 $res = db('user')->insertAll($import_data); if ($res) { return json(['status' => '2', 'message' => '导入成功']); } else { return json(['status' => '1', 'message' => '提交失败,请刷新重试']); } } ~~~ \-------------------------------------------- ~~~ public function addPost_import() { header("content-type:text/html;charset=utf-8"); //上传excel文件 $file = request()->file('file'); $pro_id = input('pro_id'); //将文件保存到public/uploads目录下面 $info = $file->validate(['size' => 1048576, 'ext' => 'xls,xlsx'])->move('./uploads'); if ($info) { //获取上传到后台的文件名 $fileName = $info->getSaveName(); //获取文件路径 //$filePath = Env::get('root_path') . 'public' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . $fileName; $filePath_ = 'uploads' . DIRECTORY_SEPARATOR . $fileName; //获取文件后缀 $suffix = $info->getExtension(); //判断哪种类型 if ($suffix == "xlsx") { $reader = \PHPExcel_IOFactory::createReader('Excel2007'); } else { $reader = PHPExcel_IOFactory::createReader('Excel5'); } } else { //return json(['status' => '1', 'message' => '文件过大或格式不正确导致上传失败-_-!']); $this->error(lang("文件过大或格式不正确导致上传失败-_-!"), url("coupon/import")); } //载入excel文件 $excel = $reader->load($filePath_, $encode = 'utf-8'); //读取第一张表 $sheet = $excel->getSheet(0); //获取总行数 $row_num = $sheet->getHighestRow(); //获取总列数 $col_num = $sheet->getHighestColumn(); $import_data = []; //数组形式获取表格数据 for ($i = 1; $i <= $row_num; $i++) { $import_data[$i]['coupon_name'] = $sheet->getCell("A" . $i)->getValue(); $import_data[$i]['create_time'] = date('Y-m-d H:i:s'); $import_data[$i]['pro_id'] = $pro_id; } //dump($import_data);die; if (empty($import_data)) { //return json(['status' => '1', 'message' => '数据解析失败']); $this->error(lang("数据解析失败"), url("coupon/import")); } //校验优惠券是否重复 $coupon_array = array_column($import_data, 'coupon_name'); // dump($coupon_array);die; // $phone_ids = implode(',', $phone_array); $result_phone = db('coupon') ->field('coupon_name') ->whereIn('coupon_name', $coupon_array) ->select() ->toArray(); //$result_phone_array = array_column($result_phone, 'coupon_name'); // dump($result_phone_array);die; if (!empty($result_phone)) { $result_phone_array = array_column($result_phone, 'coupon_name'); //$result_phone_ids = implode(',', $result_phone_array); //return json(['status' => '3', 'message' => '数据重复', 'result' => $result_phone_ids]); $this->error(lang("数据重复"), url("coupon/import")); } $result_phone_array =' error'; //将数据保存到数据库 $res = db('coupon')->insertAll($import_data); if ($res) { $this->success(lang("导入成功"), url("coupon/index")); // return json(['status' => '2', 'message' => '导入成功']); } else { $this->error(lang("导入失败,重新导入"), url("coupon/import")); } } ~~~ ![](https://img2020.cnblogs.com/blog/1251386/202006/1251386-20200608215246591-352940496.png) \----------------------------------------------------------------------------------------------------------- # PHP7.2环境中PHPExcel无法导出Excel # 问题 > 这几天接到一个bug,说的是有某一个后台的列表页有一个excel导出功能,最近不能使用了,我立即登陆系统发现确实如此,但是这个上线项目已经运行长达2年,中间并没有出现问题,于是我拉出源码进行分析,但是代码看不出明显错误,于是马上进行断点测试。 # 分析 ![](https://img2020.cnblogs.com/blog/1251386/202006/1251386-20200609110741586-710149738.png) 经过测试发现问题出在PHPExcel上面,执行方法save()会报500 Internet sever error。根据经验这一般是权限问题,我马上检查文件夹权限,但是一切正常。这就很奇怪了,于是查阅资料用了网上所有的方法依然没有恢复,就在我一筹莫展的时候,突然想起前段时间升级了7.2版本PHP,于是我动手查阅资料,果然发现问题! ![](https://img2020.cnblogs.com/blog/1251386/202006/1251386-20200609110808895-358862730.png) ![](https://img2020.cnblogs.com/blog/1251386/202006/1251386-20200609110919664-2092151058.png) PHPExcel官方已不再维护了这个项目了,PHPExcel的版本停止在1.8。那个时候也没有7.2,所有兼容性当然没有考虑。由于官方已经停止维护我们也不能获取补丁,那我们只能考虑换一个插件去处理。 选择 官方团队在github上又起了一个新项目,叫PhpSpreadsheet,新项目使用了大量的php新特性,比如命名空间,PSR标准,性能也比PHPExcel高了不少。GItHub点这里 ![](https://img2020.cnblogs.com/blog/1251386/202006/1251386-20200609110948843-1303627453.png) 如果只是单独的导出excel且数据量巨大的话,我推荐PHP\_XLSXWriter,它并没有PHPExcel功能丰富,很多高级操作比如冻结表头,并不具备,但是它导出速度非常快,非常适合于数据量特别大,报表格式不是很复杂的导出需求。GitHub点这里 其大概的用法可以参考大佬写的博文,[戳这里](https://segmentfault.com/a/1190000010178094?utm_source=coffeephp.com) https://segmentfault.com/a/1190000010178094?utm\_source=coffeephp.com 思考 最终我选择PHP\_XLSXWriter完成替换,从而解决BUG,从这次BUG中我意识到一个问题,每当升级PHP版本时,插件相应的升级刻不容缓。就像php7.2刚出来的时候YII2等主流框架会报致命错误,其原因就是7.2将Object作为了关键字,而框架将其定义为类。所以导致错误,一样是不向下兼容的错误。尽量避免错误,需要在每次更新版本前多去研究其特性,才可能规避BUG,最后说一句,祝大家永无BUG。 ———————————————— 版权声明:本文为CSDN博主「RE\_PHPzjw」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/RE\_PHPzjw/article/details/80847299 转载 [https://blog.csdn.net/RE\_PHPzjw/article/details/80847299](https://blog.csdn.net/RE_PHPzjw/article/details/80847299) # \------[使用PHP\_XLSXWriter代替PHPExcel](https://segmentfault.com/a/1190000010178094)\---------------------------- ## 二者有何区别? `PHPExcel` 是一个处理`Excel`,`CVS`文件的开源框架,它基于微软的`OpenXML`标准和`PHP`语言。可以使用它来读取、写入不同格式的电子表格,这也是`PHP`至今最通用的`Excel`处理工具,但是它有一个非常致命的缺点: 特别占内存,对大批量的表格数据几乎会让人累觉不爱,处理速度非常慢,但是它功能非常丰富,`API`非常多,所以在导出复杂格式的`Excel`表格时,你往往不得不使用它,真是让人又爱又恨。 不幸的是,`PHPExcel`官方已不再维护了这个项目了,官方团队在`github`上又起了一个新项目,叫`PhpSpreadsheet`,新项目使用了大量的`php`新特性,比如命名空间,`PSR`标准,性能也比`PHPExcel`高了不少,不过该项目至今(2017-07-12)还是开发状态,最小稳定版还没出来,估计`bug`会比较多,所以并不建议使用,下图是项目迁移说明: ![](https://segmentfault.com/img/remote/1460000010161899/view) 相比于`PHPExcel`,`PHP_XLSXWriter`是一个小而强悍的`Excel`读写插件,它并没有`PHPExcel`功能丰富,很多高级操作比如冻结表头,并不具备,但是它导出速度非常快,非常适合于数据量特别大,报表格式不是很复杂的导出需求,下图是官方的速度和内存测试: ![](https://segmentfault.com/img/remote/1460000010161900/view) ## `PHP_XLSXWriter` 如何使用? ### 下载 这是 [PHP\_XLSXWriter](https://github.com/mk-j/PHP_XLSXWriter)的`github`地址,你可以点击下载把它下载下来。解压之后你可以看到,它的核心文件只有一个: `xlswriter.class.php`,`examples`目录为代码样例目录,里面有很多例子你可以参考。 ![](https://segmentfault.com/img/remote/1460000010161901/view) ### 使用 对于日常中绝大部分的报表需求,`PHP_XLSXWriter` 是可以胜任的,下面通过一个例子来熟悉一下`API`的使用。 假设我们要导出下图中的报表,并通过浏览器实现下载: ![](https://segmentfault.com/img/remote/1460000010161902/view) 代码实现: ~~~ //writer 类 $writer = new XLSXWriter(); //文件名 $filename = "example.xlsx"; //设置 header,用于浏览器下载 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'); //导出的数据 $string = array ( 0 => array ( 'payc_bill_time' => '2017-07-12 16:40:44', 'payt_received_date' => '2017-07-12', 'ci_name' => '租金', 'payt_num' => 'YRZB(2012)A0047', 'payt_scsr_name' => '李巧红', 'payt_received' => '300.00', 'paytd_type' => '现金', 'emp_name' => '郑振标', ), 1 => array ( 'payc_bill_time' => '2017-07-12 16:39:55', 'payt_received_date' => '2017-07-12', 'ci_name' => '租金', 'payt_num' => 'YRZB(2012)A0046', 'payt_scsr_name' => '22222', 'payt_received' => '45.00', 'paytd_type' => '现金', 'emp_name' => '郑振标', ) ); //每列的标题头 $title = array ( 0 => '开单时间', 1 => '收款时间', 2 => '开票项目', 3 => '票据编号', 4 => '客户名称', 5 => '实收金额', 6 => '收款方式', 7 => '收款人', ); //工作簿名称 $sheet1 = 'sheet1'; //对每列指定数据类型,对应单元格的数据类型 foreach ($title as $key => $item){ $col_style[] = $key ==5 ? 'price': 'string'; } //设置列格式,suppress_row: 去掉会多出一行数据;widths: 指定每列宽度 $writer->writeSheetHeader($sheet1, $col_style, ['suppress_row'=>true,'widths'=>[20,20,20,20,20,20,20,20]] ); //写入第二行的数据,顺便指定样式 $writer->writeSheetRow($sheet1, ['xxx财务报表'], ['height'=>32,'font-size'=>20,'font-style'=>'bold','halign'=>'center','valign'=>'center']); /*设置标题头,指定样式*/ $styles1 = array( 'font'=>'宋体','font-size'=>10,'font-style'=>'bold', 'fill'=>'#eee', 'halign'=>'center', 'border'=>'left,right,top,bottom'); $writer->writeSheetRow($sheet1, $title,$styles1); // 最后是数据,foreach写入 foreach ($data as $value) { foreach ($value as $item) { $temp[] = $item;} $rows[] = $temp; unset($temp); } $styles2 = ['height'=>16]; foreach($rows as $row){ $writer->writeSheetRow($sheet1, $row,$styles2); } //合并单元格,第一行的大标题需要合并单元格 $writer->markMergedCell($sheet1, $start_row=0, $start_col=0, $end_row=0, $end_col=7); //输出文档 $writer->writeToStdOut(); exit(0); ~~~ 上面的每行代码都做了注释,如果不懂得话可以去查看一下`example`文件夹中的代码样例以及官网主页的文档,不过文档比较简短; ~~~ //writer 类 $writer = new XLSXWriter(); //文件名 $filename = "example.xlsx"; //设置 header,用于浏览器下载 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'); //导出的数据 $string = array ( 0 => array ( 'payc_bill_time' => '2017-07-12 16:40:44', 'payt_received_date' => '2017-07-12', 'ci_name' => '租金', 'payt_num' => 'YRZB(2012)A0047', 'payt_scsr_name' => '李巧红', 'payt_received' => '300.00', 'paytd_type' => '现金', 'emp_name' => '郑振标', ), 1 => array ( 'payc_bill_time' => '2017-07-12 16:39:55', 'payt_received_date' => '2017-07-12', 'ci_name' => '租金', 'payt_num' => 'YRZB(2012)A0046', 'payt_scsr_name' => '22222', 'payt_received' => '45.00', 'paytd_type' => '现金', 'emp_name' => '郑振标', ) ); //每列的标题头 $title = array ( 0 => '开单时间', 1 => '收款时间', 2 => '开票项目', 3 => '票据编号', 4 => '客户名称', 5 => '实收金额', 6 => '收款方式', 7 => '收款人', ); //工作簿名称 $sheet1 = 'sheet1'; //对每列指定数据类型,对应单元格的数据类型 foreach ($title as $key => $item){ $col_style[] = $key ==5 ? 'price': 'string'; } //设置列格式,suppress_row: 去掉会多出一行数据;widths: 指定每列宽度 $writer->writeSheetHeader($sheet1, $col_style, ['suppress_row'=>true,'widths'=>[20,20,20,20,20,20,20,20]] ); //写入第二行的数据,顺便指定样式 $writer->writeSheetRow($sheet1, ['xxx财务报表'], ['height'=>32,'font-size'=>20,'font-style'=>'bold','halign'=>'center','valign'=>'center']); /*设置标题头,指定样式*/ $styles1 = array( 'font'=>'宋体','font-size'=>10,'font-style'=>'bold', 'fill'=>'#eee', 'halign'=>'center', 'border'=>'left,right,top,bottom'); $writer->writeSheetRow($sheet1, $title,$styles1); // 最后是数据,foreach写入 foreach ($data as $value) { foreach ($value as $item) { $temp[] = $item;} $rows[] = $temp; unset($temp); } $styles2 = ['height'=>16]; foreach($rows as $row){ $writer->writeSheetRow($sheet1, $row,$styles2); } //合并单元格,第一行的大标题需要合并单元格 $writer->markMergedCell($sheet1, $start_row=0, $start_col=0, $end_row=0, $end_col=7); //输出文档 $writer->writeToStdOut(); exit(0); ~~~ ## 跳坑指南: 自己在使用过程中也踩过一些坑,这里列一下,希望对你有帮助: ### 文件名与类名不对应 在使用`require`或`require_once`时,这其实不是问题,但当使用自动加载时因为二者不对应就无法识别。你或许想把`xlsxwriter.class.php`文件引入到你的项目中,并且加上命名空间,以便于能实现自动加载。此时你需要做的是把文件名更改为类名`XLSXWriter.class.php`(这里引入到TP中),比如我放到`Component`目录下,那么在该文件中添加命名空间`namespace Component;`,此时该文件中还有个`Zip`类未引入命名空间,需要添加`use ZipArchive;` 这些完成后,就可以在项目中其他地方使用了: ~~~ use Component; $writer = new XLSXWriter(); ~~~ ### 如何设置列格式? 不同的列可能会需要显示不同的格式,默认的都是文本格式,但有时候需要显示为数字列,比便于使用excel中的函数,比如上表中的金额列,必须是两位小数点,千分位,数字格式。 看上面的代码,数字格式其实是在`writeSheetHeader`方法中设置的,类型为`price`的那一列就是金额列,如果你需要其他格式,官网首页上列出了常用的格式。 ### 能不能单独设置某个单元格的值? 这个目前并没有实现,现在数据的写入都是`逐行`写入的,不支持这么细的粒度,不过折中的做法是把不需要填充的单元格写入`null`即可; 如果有什么使用问题,可以在下面留言,一起探讨使用方式:) 转载 :[https://segmentfault.com/a/1190000010178094?utm\_source=coffeephp.com](https://segmentfault.com/a/1190000010178094?utm_source=coffeephp.com) # **附录** ## 基于jq的input file文件上传 input file文件上传很多人都遇见过,就样式问题就有一大堆的问题,默认样式实在是不敢恭维,但在此今天不讨论样式的问题,只给一个大致的思路,一个div包括一个span和一个input,分别设置两个的定位,然后给input设置opacity为0,设置好宽度充满div,在设置好span的样式即可。 众所周知,ajax不能传递文件,但是html的formdata可以。 ### [](https://zhjdeitiny.github.io/2018/08/21/jq%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E5%B0%8F%E7%BB%93/#html%E4%BB%A3%E7%A0%81 "html代码")html代码 ~~~ <p class="selected-box"> <form class="selected-wrap" id="form1" action="Upload" method="post" enctype="multipart/form-data"> <input type="file" class="selected-btn" id ="file" name="file"> </form> <span class="selected-file">选择本地</span> </p> <div class="leading-in">导入</div> ~~~ 我的代码是点击选择本地出发input file的按钮选择文件,然后点击导入出发ajax,中间还会判断是否已选择文件,没有选择的话,提示它,选择文件不对的话,也提示(我这边判断的是Execl文件,下方代码有)。其中需要注意的是必须要用form标签包裹,注意action、method和enctype属性,其中特别需要注意的是**enctype**属性必须为**multipart/form-data**,这样子才能将文件处理为一个二进制的文件,后台才能够进行接收。   PS:action这个属性其实也可以不填,它是放地址的,我们在ajax中会有提交地址的;     enctype这个属性其实也可以不在标签中添加,不过在js中的属性contentType就必须设置为multipart/form-data, * 简单来讲 > 如果你的input file标签里面**使用了enctype=”multipart/form-data”**,JQ中这两个属性就要这样写**processData: false, contentType: false** > 如果如果你的input file标签里面**没有使用 enctype=”multipart/form-data”**,JQ就要这样写**processData: false,contentType: “multipart/form-data”**。 ### [](https://zhjdeitiny.github.io/2018/08/21/jq%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E5%B0%8F%E7%BB%93/#js%E4%BB%A3%E7%A0%81 "js代码")js代码 ~~~ $(".leading-in").on("click",function(){ var name = $(".selected-txt select option:selected").val(); var flag = $(".selected-btn").val(); var hzm = flag.substr(flag.indexOf(".")+1); if(flag == ""){ alert("请选择要上传的文件"); }else if(hzm != "xls" && hzm != "xlsx"){ alert("请选择Excel格式的文件"); }else{ var forData = new FormData(); forData.set("importfile", $("#file")[0].files[0]); forData.set("systemName", name); // 多个文件上传的情况,需要啊后台进行字段匹配如:上方的importfile // var i; // for (i = 0; i < $('.select-file').files.length; i++) { // // forData.append('file[]', this.files[i]); // } $.ajax({ url: baselocation + "/system/file/import", type: 'post', data: forData, cache: false, processData: false, contentType: false, success:function(json){ if(json.success == "success"){ alert("上传成功"); }else{ alert("上传失败"); } },error:function(){ } }); } }) ~~~ js代码中我先判断获取文件的后缀名是不是我自己想要的类型文件,然后进行相应的提示,然后就新建一个FormData对象,然后把文件添加到里面,规则名一个要和后台确定好,如果还有其他的属性的,一并放到FormData的对象中就好,毕竟一个单独的属性,没有必要单独拿出来去提交,直接放到对象里面一起提交就好。 ### [](https://zhjdeitiny.github.io/2018/08/21/jq%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E5%B0%8F%E7%BB%93/#%E5%B0%8F%E7%BB%93 "小结")小结 * 1、**cache**:cache设为false可以禁止浏览器对该URL(以及对应的HTTP方法)的缓存。 jQuery通过为URL添加一个冗余参数来实现。 * 2、**contentType**:jQuery中content-type默认值为application/x-www-form-urlencoded,因此传给data参数的对象会默认被转换为query string,我们不需要jQuery做这个转换,否则会破坏掉multipart/form-data的编码格式。 因此设置contentType: false来禁止jQuery的转换操作。 * 3、**processData**:jQuery会将data对象转换为字符串来发送HTTP请求,默认情况下会用 application/x-www-form-urlencoded编码来进行转换。 我们设置contentType: false后该转换会失败,因此设置processData: false来禁止该转换过程。我们给的data就是已经用FormData编码好的数据,不需要jQuery进行字符串转换。(它与enctype的关系上方已经阐述) 如果有多文件的情况,代码中也有注释,前提是一定要和后台定好参数规则,建议比如说:file\[文件索引\]这样的规则,后台拿到数据之后出掉一些定义好的其他属性,根据length,一个一个去取好;