【黄啊码】PHP配合xlswriter实现无限表头层级Excel导出

举报
黄啊码 发表于 2022/06/28 23:54:32 2022/06/28
【摘要】 本文介绍基于PHP扩展xlswriter的Vtiful\Kernel\Excel类可以支持无限层级的复杂表头导出!后续也可能会持续更新优化 一、准备xlswriter扩展 1、windows系统: 到PECL网站下载符合自己本地PHP环境的ddl文件下载地址:https://pecl.php.net/package/xlswrit...

本文介绍基于PHP扩展xlswriter的Vtiful\Kernel\Excel类可以支持无限层级的复杂表头导出!后续也可能会持续更新优化

一、准备xlswriter扩展

1、windows系统:

到PECL网站下载符合自己本地PHP环境的ddl文件下载地址:https://pecl.php.net/package/xlswriter,并复制到PHP的扩展目录ext文件夹下,修改php.ini文件,

加上这行

extension=xlswriter
 

打开phpinfo()验证扩展是否安装成功

2、Linux系统:

pecl install xlswriter
 

php配置文件添加

extension = xlswriter.so
 

重启php nginx

二、composer下载phpoffice/phpexcel

因为有用到单元格相关函数,所以需要执行下列命令

composer require phpoffice/phpexcel 1.8
 

 三、封装导出类文件(重点来了)


  
  1. <?php
  2. use PHPExcel_Cell;
  3. class MultiFloorXlsWriterService
  4. {
  5. // 默认宽度
  6. private $defaultWidth = 16;
  7. // 默认导出格式
  8. private $exportType = '.xlsx';
  9. // 表头最大层级
  10. private $maxHeight = 1;
  11. // 文件名
  12. private $fileName = null;
  13. private $xlsObj;
  14. private $fileObject;
  15. private $format;
  16. public function __construct()
  17. {
  18. // 文件默认输出地址
  19. $path = base_path().'/storage/logs';
  20. $config = [
  21. 'path' => $path
  22. ];
  23. $this->xlsObj = (new \Vtiful\Kernel\Excel($config));
  24. }
  25. /**
  26. * 设置文件名
  27. * @param string $fileName 文件名
  28. * @param string $sheetName 第一个sheet名
  29. */
  30. public function setFileName(string $fileName = '', string $sheetName = 'Sheet1')
  31. {
  32. $fileName = empty($fileName) ? (string)time() : $fileName;
  33. $fileName .= $this->exportType;
  34. $this->fileName = $fileName;
  35. $this->fileObject = $this->xlsObj->fileName($fileName, $sheetName);
  36. $this->format = (new \Vtiful\Kernel\Format($this->fileObject->getHandle()));
  37. }
  38. /**
  39. * 设置表头
  40. * @param array $header
  41. * @param bool $filter
  42. * @throws Exception
  43. */
  44. public function setHeader(array $header, bool $filter = false)
  45. {
  46. if (empty($header)) {
  47. throw new \Exception('表头数据不能为空');
  48. }
  49. if (is_null($this->fileName)) {
  50. self::setFileName(time());
  51. }
  52. // 获取单元格合并需要的信息
  53. $colManage = self::setHeaderNeedManage($header);
  54. // 完善单元格合并信息
  55. $colManage = self::completeColMerge($colManage);
  56. // 合并单元格
  57. self::queryMergeColumn($colManage, $filter);
  58. }
  59. /**
  60. * 填充文件数据
  61. * @param array $data
  62. */
  63. public function setData(array $data)
  64. {
  65. foreach ($data as $row => $datum) {
  66. foreach ($datum as $column => $value) {
  67. $this->fileObject->insertText($row + $this->maxHeight, $column, $value);
  68. }
  69. }
  70. }
  71. /**
  72. * 添加Sheet
  73. * @param string $sheetName
  74. */
  75. public function addSheet(string $sheetName)
  76. {
  77. $this->fileObject->addSheet($sheetName);
  78. }
  79. /**
  80. * 保存文件至服务器
  81. */
  82. public function output()
  83. {
  84. return $this->fileObject->output();
  85. }
  86. /**
  87. * 输出到浏览器
  88. * @param $filePath
  89. * @param $fileName
  90. * @throws Exception
  91. */
  92. public function excelDownload($filePath)
  93. {
  94. $fileName = $this->fileName;
  95. $userBrowser = $_SERVER['HTTP_USER_AGENT'];
  96. if( preg_match('/MSIE/i', $userBrowser)) {
  97. $fileName = urlencode($fileName);
  98. } else {
  99. $fileName = iconv('UTF-8', 'GBK//IGNORE', $fileName);
  100. }
  101. header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
  102. header('Content-Disposition: attachment;filename="' . $fileName . '"');
  103. header('Content-Length: ' . filesize($filePath));
  104. header('Content-Transfer-Encoding: binary');
  105. header('Cache-Control: must-revalidate');
  106. header('Cache-Control: max-age=0');
  107. header('Pragma: public');
  108. if (ob_get_contents()) {
  109. ob_clean();
  110. }
  111. flush();
  112. if (copy($filePath, 'php://output') === false) {
  113. throw new \Exception($filePath. '地址出问题了');
  114. }
  115. // 删除本地文件
  116. @unlink($filePath);
  117. exit();
  118. }
  119. /**
  120. * 组装单元格合并需要的信息
  121. * @param $header
  122. * @param int $cursor
  123. * @param int $col
  124. * @param array $colManage
  125. * @param $parentList
  126. * @param $parent
  127. * @throws \Exception
  128. * @return array
  129. */
  130. private function setHeaderNeedManage($header, $col = 1, &$cursor = 0, &$colManage = [], $parent = null, $parentList = [])
  131. {
  132. foreach ($header as $head) {
  133. if (empty($head['title'])) {
  134. throw new \Exception('表头数据格式有误');
  135. }
  136. if (is_null($parent)) {
  137. // 循环初始化
  138. $parentList = [];
  139. $col = 1;
  140. } else {
  141. // 递归进入,高度和父级集合通过相同父级条件从已有数组中获取,避免递归增加与实际数据不符
  142. foreach ($colManage as $value) {
  143. if ($value['parent'] == $parent) {
  144. $parentList = $value['parentList'];
  145. $col = $value['height'];
  146. break;
  147. }
  148. }
  149. }
  150. // 单元格标识
  151. $column = $this->getColumn($cursor) . $col;
  152. // 组装单元格需要的各种信息
  153. $colManage[$column] = [
  154. 'title' => $head['title'], // 标题
  155. 'cursor' => $cursor, // 游标
  156. 'cursorEnd' => $cursor, // 结束游标
  157. 'height' => $col, // 高度
  158. 'width' => $this->defaultWidth, // 宽度
  159. 'mergeStart' => $column, // 合并开始标识
  160. 'hMergeEnd' => $column, // 横向合并结束标识
  161. 'zMergeEnd' => $column, // 纵向合并结束标识
  162. 'parent' => $parent, // 父级标识
  163. 'parentList' => $parentList, // 父级集合
  164. ];
  165. if (isset($head['children']) && !empty($head['children']) && is_array($head['children'])) {
  166. // 有下级,高度加一
  167. $col += 1;
  168. // 当前标识加入父级集合
  169. $parentList[] = $column;
  170. $this->setHeaderNeedManage($head['children'], $col, $cursor,$colManage, $column, $parentList);
  171. } else {
  172. // 没有下级,游标加一
  173. $cursor += 1;
  174. }
  175. }
  176. return $colManage;
  177. }
  178. /**
  179. * 完善单元格合并信息
  180. * @param $colManage
  181. * @return array
  182. */
  183. private function completeColMerge($colManage)
  184. {
  185. $this->maxHeight = max(array_column($colManage, 'height'));
  186. $parentManage = array_column($colManage, 'parent');
  187. foreach ($colManage as $index => $value) {
  188. // 设置横向合并结束范围:存在父级集合,把所有父级的横向合并结束范围设置为当前单元格
  189. if (!is_null($value['parent']) && !empty($value['parentList'])) {
  190. foreach ($value['parentList'] as $parent) {
  191. $colManage[$parent]['hMergeEnd'] = self::getColumn($value['cursor']) . $colManage[$parent]['height'];
  192. $colManage[$parent]['cursorEnd'] = $value['cursor'];
  193. }
  194. }
  195. // 设置纵向合并结束范围:当前高度小于最大高度 且 不存在以当前单元格标识作为父级的项
  196. $checkChildren = array_search($index, $parentManage);
  197. if ($value['height'] < $this->maxHeight && !$checkChildren) {
  198. $colManage[$index]['zMergeEnd'] = self::getColumn($value['cursor']) . $this->maxHeight;
  199. }
  200. }
  201. return $colManage;
  202. }
  203. /**
  204. * 合并单元格
  205. * @param $colManage
  206. * @param $filter
  207. */
  208. private function queryMergeColumn($colManage, $filter)
  209. {
  210. foreach ($colManage as $value) {
  211. $this->fileObject->mergeCells("{$value['mergeStart']}:{$value['zMergeEnd']}", $value['title']);
  212. $this->fileObject->mergeCells("{$value['mergeStart']}:{$value['hMergeEnd']}", $value['title']);
  213. // 设置单元格需要的宽度
  214. if ($value['cursor'] != $value['cursorEnd']) {
  215. $value['width'] = ($value['cursorEnd'] - $value['cursor'] + 1) * $this->defaultWidth;
  216. }
  217. // 设置列单元格样式
  218. $toColumnStart = self::getColumn($value['cursor']);
  219. $toColumnEnd = self::getColumn($value['cursorEnd']);
  220. $this->fileObject->setColumn("{$toColumnStart}:{$toColumnEnd}", $value['width']);
  221. }
  222. // 是否开启过滤选项
  223. if ($filter) {
  224. // 获取最后的单元格标识
  225. $filterEndColumn = self::getColumn(end($colManage)['cursorEnd']) . $this->maxHeight;
  226. $this->fileObject->autoFilter("A1:{$filterEndColumn}");
  227. }
  228. }
  229. /**
  230. * 获取单元格列标识
  231. * @param $num
  232. * @return string
  233. */
  234. private function getColumn($num)
  235. {
  236. return PHPExcel_Cell::stringFromColumnIndex($num);
  237. }
  238. }

四、使用示例

代码如下:


  
  1. $header = [
  2. [
  3. 'title' => '一级表头1',
  4. 'children' => [
  5. [
  6. 'title' => '二级表头1',
  7. ],
  8. [
  9. 'title' => '二级表头2',
  10. ],
  11. [
  12. 'title' => '二级表头3',
  13. ],
  14. ]
  15. ],
  16. [
  17. 'title' => '一级表头2'
  18. ],
  19. [
  20. 'title' => '一级表头3',
  21. 'children' => [
  22. [
  23. 'title' => '二级表头1',
  24. 'children' => [
  25. [
  26. 'title' => '三级表头1',
  27. ],
  28. [
  29. 'title' => '三级表头2',
  30. ],
  31. ]
  32. ],
  33. [
  34. 'title' => '二级表头2',
  35. ],
  36. [
  37. 'title' => '二级表头3',
  38. 'children' => [
  39. [
  40. 'title' => '三级表头1',
  41. 'children' => [
  42. [
  43. 'title' => '四级表头1',
  44. 'children' => [
  45. [
  46. 'title' => '五级表头1'
  47. ],
  48. [
  49. 'title' => '五级表头2'
  50. ]
  51. ]
  52. ],
  53. [
  54. 'title' => '四级表头2'
  55. ]
  56. ]
  57. ],
  58. [
  59. 'title' => '三级表头2',
  60. ],
  61. ]
  62. ]
  63. ]
  64. ],
  65. [
  66. 'title' => '一级表头4',
  67. ],
  68. [
  69. 'title' => '一级表头5',
  70. ],
  71. ];
  72. // header头规则 title表示列标题,children表示子列,没有子列children可不写或为空
  73. for ($i = 0; $i < 100; $i++) {
  74. $data[] = [
  75. '这是第'. $i .'行测试',
  76. '这是第'. $i .'行测试',
  77. '这是第'. $i .'行测试',
  78. '这是第'. $i .'行测试',
  79. '这是第'. $i .'行测试',
  80. '这是第'. $i .'行测试',
  81. '这是第'. $i .'行测试',
  82. '这是第'. $i .'行测试',
  83. '这是第'. $i .'行测试',
  84. '这是第'. $i .'行测试',
  85. '这是第'. $i .'行测试',
  86. '这是第'. $i .'行测试',
  87. '这是第'. $i .'行测试',
  88. ];
  89. }
  90. $fileName = '很厉害的文件导出类';
  91. $xlsWriterServer = new MultiFloorXlsWriterService();
  92. $xlsWriterServer->setFileName($fileName, '这是Sheet1别名');
  93. $xlsWriterServer->setHeader($header, true);
  94. $xlsWriterServer->setData($data);
  95. $xlsWriterServer->addSheet('这是Sheet2别名');
  96. $xlsWriterServer->setHeader($header); //这里可以使用新的header
  97. $xlsWriterServer->setData($data); // 这里也可以根据新的header定义数据格式
  98. $filePath = $xlsWriterServer->output(); // 保存到服务器
  99. $xlsWriterServer->excelDownload($filePath); // 输出到浏览器

导出效果图:

 

文章来源: markwcm.blog.csdn.net,作者:黄啊码,版权归原作者所有,如需转载,请联系作者。

原文链接:markwcm.blog.csdn.net/article/details/124706484

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200