ExcelHelper.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. <?php
  2. namespace common\helpers;
  3. use Exception;
  4. use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
  5. use PhpOffice\PhpSpreadsheet\Cell\DataType;
  6. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  7. use PhpOffice\PhpSpreadsheet\Writer\Html;
  8. use PhpOffice\PhpSpreadsheet\Writer\Xls;
  9. use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
  10. use PhpOffice\PhpSpreadsheet\Writer\Csv;
  11. /**
  12. * 导出导入Excel
  13. *
  14. * Class ExcelHelper
  15. * @package common\helpers
  16. * @author jianyan74 <751393839@qq.com>
  17. */
  18. class ExcelHelper
  19. {
  20. /**
  21. * 导出Excel
  22. *
  23. * @param array $list
  24. * @param array $header
  25. * @param string $filename
  26. * @param string $suffix
  27. * @param string $path 输出绝对路径
  28. * @return bool
  29. * @throws \PhpOffice\PhpSpreadsheet\Exception
  30. * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
  31. */
  32. public static function exportData($list = [], $header = [], $filename = '', $suffix = 'xlsx', $path = '')
  33. {
  34. if (!is_array($list) || !is_array($header)) {
  35. return false;
  36. }
  37. !$filename && $filename = time();
  38. // 初始化
  39. $spreadsheet = new Spreadsheet();
  40. $sheet = $spreadsheet->getActiveSheet();
  41. // 写入头部
  42. $hk = 1;
  43. foreach ($header as $k => $v) {
  44. $sheet->setCellValue(Coordinate::stringFromColumnIndex($hk) . '1', $v[0]);
  45. $sheet->getStyle(Coordinate::stringFromColumnIndex($hk) . '1')->getFont()->setBold(true);
  46. $sheet->getColumnDimension(Coordinate::stringFromColumnIndex($hk))->setAutoSize(true);
  47. $hk += 1;
  48. }
  49. // 开始写入内容
  50. $column = 2;
  51. $size = ceil(count($list) / 500);
  52. for ($i = 0; $i < $size; $i++) {
  53. $buffer = array_slice($list, $i * 500, 500);
  54. foreach ($buffer as $k => $row) {
  55. $span = 1;
  56. foreach ($header as $key => $value) {
  57. // 解析字段
  58. $realData = self::formatting($header[$key], trim(self::formattingField($row, $value[1])), $row);
  59. // 写入excel
  60. $sheet->setCellValueExplicit(Coordinate::stringFromColumnIndex($span) . $column, $realData, DataType::TYPE_STRING);
  61. // $sheet->setCellValue(Coordinate::stringFromColumnIndex($span) . $column, $realData);
  62. $span++;
  63. }
  64. $column++;
  65. unset($buffer[$k]);
  66. }
  67. }
  68. // 清除之前的错误输出
  69. ob_end_clean();
  70. ob_start();
  71. // 直接输出下载
  72. switch ($suffix) {
  73. case 'xlsx' :
  74. $writer = new Xlsx($spreadsheet);
  75. if (!empty($path)) {
  76. $writer->save($path);
  77. } else {
  78. header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8;");
  79. header("Content-Disposition: inline;filename=\"{$filename}.xlsx\"");
  80. header('Cache-Control: max-age=0');
  81. $writer->save('php://output');
  82. }
  83. break;
  84. case 'xls' :
  85. $writer = new Xls($spreadsheet);
  86. if (!empty($path)) {
  87. $writer->save($path);
  88. } else {
  89. header("Content-Type:application/vnd.ms-excel;charset=utf-8;");
  90. header("Content-Disposition:inline;filename=\"{$filename}.xls\"");
  91. header('Cache-Control: max-age=0');
  92. $writer->save('php://output');
  93. }
  94. break;
  95. case 'csv' :
  96. $writer = new Csv($spreadsheet);
  97. if (!empty($path)) {
  98. $writer->save($path);
  99. } else {
  100. header("Content-type:text/csv;charset=utf-8;");
  101. header("Content-Disposition:attachment; filename={$filename}.csv");
  102. header('Cache-Control: max-age=0');
  103. $writer->save('php://output');
  104. }
  105. break;
  106. case 'html' :
  107. $writer = new Html($spreadsheet);
  108. if (!empty($path)) {
  109. $writer->save($path);
  110. } else {
  111. header("Content-Type:text/html;charset=utf-8;");
  112. header("Content-Disposition:attachment;filename=\"{$filename}.{$suffix}\"");
  113. header('Cache-Control: max-age=0');
  114. $writer->save('php://output');
  115. }
  116. break;
  117. }
  118. /* 释放内存 */
  119. $spreadsheet->disconnectWorksheets();
  120. unset($spreadsheet);
  121. ob_end_flush();
  122. exit();
  123. }
  124. /**
  125. * 导出的另外一种形式(不建议使用)
  126. *
  127. * @param array $list
  128. * @param array $header
  129. * @param string $filename
  130. * @return bool
  131. */
  132. public static function exportCsvData($list = [], $header = [], $filename = '')
  133. {
  134. if (!is_array($list) || !is_array($header)) {
  135. return false;
  136. }
  137. // 清除之前的错误输出
  138. ob_end_clean();
  139. ob_start();
  140. !$filename && $filename = time();
  141. $html = "\xEF\xBB\xBF";
  142. foreach ($header as $k => $v) {
  143. $html .= $v[0] . "\t ,";
  144. }
  145. $html .= "\n";
  146. if (!empty($list)) {
  147. $info = [];
  148. $size = ceil(count($list) / 500);
  149. for ($i = 0; $i < $size; $i++) {
  150. $buffer = array_slice($list, $i * 500, 500);
  151. foreach ($buffer as $k => $row) {
  152. $data = [];
  153. foreach ($header as $key => $value) {
  154. // 解析字段
  155. $realData = self::formatting($header[$key], trim(self::formattingField($row, $value[1])), $row);
  156. $data[] = '"' . $realData . '"';
  157. }
  158. $info[] = implode("\t ,", $data) . "\t ,";
  159. unset($data, $buffer[$k]);
  160. }
  161. }
  162. $html .= implode("\n", $info);
  163. }
  164. header("Content-type:text/csv");
  165. header("Content-Disposition:attachment; filename={$filename}.csv");
  166. echo $html;
  167. exit();
  168. }
  169. /**
  170. * 导入
  171. *
  172. * @param $filePath
  173. * @param int $startRow
  174. * @return array|mixed
  175. * @throws Exception
  176. * @throws \PhpOffice\PhpSpreadsheet\Exception
  177. * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
  178. */
  179. public static function import($filePath, $startRow = 1)
  180. {
  181. $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
  182. $reader->setReadDataOnly(true);
  183. if (!$reader->canRead($filePath)) {
  184. $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls();
  185. // setReadDataOnly Set read data only 只读单元格的数据,不格式化 e.g. 读时间会变成一个数据等
  186. $reader->setReadDataOnly(true);
  187. if (!$reader->canRead($filePath)) {
  188. throw new Exception('不能读取Excel');
  189. }
  190. }
  191. $spreadsheet = $reader->load($filePath);
  192. $sheetCount = $spreadsheet->getSheetCount();// 获取sheet的数量
  193. // 获取所有的sheet表格数据
  194. $excleDatas = [];
  195. $emptyRowNum = 0;
  196. for ($i = 0; $i < $sheetCount; $i++) {
  197. $currentSheet = $spreadsheet->getSheet($i); // 读取excel文件中的第一个工作表
  198. $allColumn = $currentSheet->getHighestColumn(); // 取得最大的列号
  199. $allColumn = Coordinate::columnIndexFromString($allColumn); // 由列名转为列数('AB'->28)
  200. $allRow = $currentSheet->getHighestRow(); // 取得一共有多少行
  201. $arr = [];
  202. for ($currentRow = $startRow; $currentRow <= $allRow; $currentRow++) {
  203. // 从第1列开始输出
  204. for ($currentColumn = 1; $currentColumn <= $allColumn; $currentColumn++) {
  205. $val = $currentSheet->getCellByColumnAndRow($currentColumn, $currentRow)->getValue();
  206. $arr[$currentRow][] = trim($val);
  207. }
  208. // $arr[$currentRow] = array_filter($arr[$currentRow]);
  209. // 统计连续空行
  210. if (empty($arr[$currentRow]) && $emptyRowNum <= 50) {
  211. $emptyRowNum++;
  212. } else {
  213. $emptyRowNum = 0;
  214. }
  215. // 防止坑队友的同事在excel里面弄出很多的空行,陷入很漫长的循环中,设置如果连续超过50个空行就退出循环,返回结果
  216. // 连续50行数据为空,不再读取后面行的数据,防止读满内存
  217. if ($emptyRowNum > 50) {
  218. break;
  219. }
  220. }
  221. $excleDatas[$i] = $arr; // 多个sheet的数组的集合
  222. }
  223. // 这里我只需要用到第一个sheet的数据,所以只返回了第一个sheet的数据
  224. $returnData = $excleDatas ? array_shift($excleDatas) : [];
  225. // 第一行数据就是空的,为了保留其原始数据,第一行数据就不做array_fiter操作;
  226. $returnData = $returnData && isset($returnData[$startRow]) && !empty($returnData[$startRow]) ? array_filter($returnData) : $returnData;
  227. return $returnData;
  228. }
  229. /**
  230. * 格式化内容
  231. *
  232. * @param array $array 头部规则
  233. * @return false|mixed|null|string 内容值
  234. */
  235. protected static function formatting(array $array, $value, $row)
  236. {
  237. !isset($array[2]) && $array[2] = 'text';
  238. switch ($array[2]) {
  239. // 文本
  240. case 'text' :
  241. return $value;
  242. break;
  243. // 日期
  244. case 'date' :
  245. return !empty($value) ? date($array[3], $value) : null;
  246. break;
  247. // 选择框
  248. case 'selectd' :
  249. return $array[3][$value] ?? null;
  250. break;
  251. // 匿名函数
  252. case 'function' :
  253. return isset($array[3]) ? call_user_func($array[3], $row) : null;
  254. break;
  255. // 默认
  256. default :
  257. break;
  258. }
  259. return null;
  260. }
  261. /**
  262. * 解析字段
  263. *
  264. * @param $row
  265. * @param $field
  266. * @return mixed
  267. */
  268. protected static function formattingField($row, $field)
  269. {
  270. $newField = explode('.', $field);
  271. if (count($newField) == 1) {
  272. $result = $row[$field] ?? '';
  273. return is_array($result) ? json_encode($result) : $result;
  274. }
  275. foreach ($newField as $item) {
  276. if (isset($row[$item])) {
  277. $row = $row[$item];
  278. } else {
  279. $row = '';
  280. break;
  281. }
  282. }
  283. return is_array($row) ? json_encode($row) : $row;
  284. }
  285. }
粤ICP备19079148号