PHP 基于XLSXWriter的多级表头Excel导出

示例只有三级,但是不止于三级

应用举例

比如说我们需要这样一个表头

1.XLSXWriter应用分析

根据XLSXWriter工具类的示例

我们需要插入三行数据作为表头

然后再合并掉对应的单元格


2.工具类编写

调试的注释啥的,都在里面,有兴趣的自己可以看一下。需要放在和xlsxwriter.class.php同一目录下使用

 
  1. <?php

  2. /**

  3. * Created By

  4. * 模块说明:

  5. * User: helloc

  6. * Datetime: 2020/1/17 11:07

  7. */

  8. require_once __DIR__.DIRECTORY_SEPARATOR.'xlsxwriter.class.php';

  9.  
  10. class XLSXWriterMultiHeaderTool

  11. {

  12. private $headerData =[];

  13.  
  14. private $headerWidthSize = 0;

  15. private $headerHeightSize = 0;

  16.  
  17.  
  18. private $colDefalutFormat = 'string';// 列默认数据格式

  19. private $colDefalutWidth = '10';// 列默认宽度

  20.  
  21. // 设置表头需要要参数 从根节点中取

  22. //$writer->writeSheetHeader($sheet1, $header, $col_options = ['suppress_row'=>true,'widths'=>$headerWidth] )

  23. private $header = [];

  24. private $headerWidth =[];

  25.  
  26. // 数据字段

  27. private $dataFields = [];

  28.  
  29. // 标题行默认样式

  30. private $headerDefalutStyle = [

  31. 'halign'=>'center',//水平居中

  32. 'valign'=>'center',//竖直居中

  33. 'font-style'=>'bold',

  34. 'font-size'=>12,

  35. 'border'=>'top,bottom,left,right'

  36. //'fill'=>'#fff'// 背景色

  37. // 边框有点问题不能用

  38. // 高度也不行 用font-size撑开

  39. ];

  40. // 标题行处理数组

  41. /**

  42. $headerRow = array(

  43. array('content' =>['Merge Cells Example','','','',''],'style'=>[['halign'=>'center'],[],[],[],[]]),

  44. array('content' =>['姓名', '吃饭', '', '考勤', ''],'style'=>[['halign'=>'center','valign'=>'center'],['halign'=>'center'],[],['halign'=>'center'],[]]),

  45. array('content' =>['', '上午', '下午', '上午', '下午'],'style'=>[[],['halign'=>'center'],[],[],[]]),

  46. );

  47. */

  48. private $headerRow =[

  49. 0=>['content'=>[],'style'=>[]],

  50. ];

  51. private $headerMargin =[];

  52.  
  53. // 数据行

  54. private $dataRow = [

  55.  
  56. ];

  57. // 现在够用了,先不做了

  58. private $dataStyle = [];

  59.  
  60.  
  61.  
  62. public function __construct()

  63. {

  64. }

  65.  
  66. public function __get($name)

  67. {

  68. if (isset($this->$name)) {

  69. return $this->$name;

  70. } else {

  71. return null;

  72. }

  73. // TODO: Implement __get() method.

  74. }

  75.  
  76. /**

  77. * 设置表头

  78. * @param array $head

  79. */

  80. public function setHeader($header = [])

  81. {

  82. $this->headerData = $header;

  83. // 设置表头尺寸

  84. $this->setHeaderSize($this->headerData);

  85. // 生成rows 和 margin 设置样式

  86. $this->dealHeader($this->headerData);

  87. }

  88.  
  89.  
  90. public function setData($data = []){

  91. foreach ($data as $d){

  92. $row = [];

  93. foreach ($this->dataFields as $field){

  94. $value = isset($d[$field])?$d[$field]:'';

  95. $row[] = $value;

  96. }

  97. $this->dataRow[] = $row;

  98. }

  99. // dump($this->dataFields);

  100. // dump($this->dataRow);

  101. dump($data);exit;

  102. // exit;

  103. }

  104.  
  105.  
  106. /**

  107. * 导出

  108. * @param string $filename

  109. * @param string $sheet

  110. */

  111. public function writeToStdOut($filename='test',$sheet='Sheet1'){

  112. //$header = ['字段','标题','宽度','类型']

  113. $filename .= '.xls';

  114. $writer = new \XLSXWriter();

  115. $writer->writeSheetHeader($sheet, $this->header, $col_options = ['suppress_row'=>true,'widths'=>$this->headerWidth] );

  116. foreach($this->headerRow as $row)

  117. // $row['style']['height']=30;

  118. $writer->writeSheetRow($sheet, $row['content'],$row['style']);

  119. foreach ($this->headerMargin as $margin){

  120. $writer->markMergedCell($sheet, $margin['startRow'], $margin['startCol'], $margin['endRow'], $margin['endCol']);

  121. }

  122. foreach($this->dataRow as $data)

  123. $writer->writeSheetRow($sheet, $data);

  124. header('Content-disposition: attachment; filename="' . \XLSXWriter::sanitize_filename($filename));

  125. header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

  126. header('Content-Transfer-Encoding: binary');

  127. header('Cache-Control: must-revalidate');

  128. header('Pragma: public');

  129. $writer->writeToStdOut();

  130. }

  131.  
  132.  
  133. /**

  134. * 生成rows 和 margin 设置样式

  135. * @param $headerData

  136. */

  137. private function dealHeader($headerData){

  138. // $widthSize = $this->headerWidthSize;

  139. // $heightSize = $this->headerHeightSize;

  140. // echo "宽度:{$widthSize},高度:{$heightSize}";

  141. // $headerData = [

  142. // [

  143. // 'title' => 'test1',

  144. // 'marginCol' => '1',

  145. // 'style'=>[

  146. // 'halign'=>'left','valign'=>'center'

  147. // ]

  148. // ],

  149. // [

  150. // 'title' => 'test2',

  151. // 'marginCol' => '3',

  152. // 'children'=>[

  153. // [

  154. // 'title'=>'上午',

  155. // 'field'=>'workam'

  156. // ],

  157. // [

  158. // 'title'=>'上午',

  159. // 'field'=>'workpm'

  160. // ],

  161. // ]

  162. // ],

  163. // [

  164. // 'title' => 'test3',

  165. // 'marginCol' => '1',

  166. // ],

  167. //

  168. // ];

  169.  
  170. $this->recursionSetHeaderRowsAndMargin($headerData,0,0,$this->headerDefalutStyle);

  171. // dump($this->headerMargin);

  172. // dump($this->headerRow);exit;

  173. // exit;

  174. }

  175.  
  176. /**

  177. * 递归设置表头行数据,和合并表格数据源

  178. * @param $headerData

  179. * @param $row

  180. * @param $startCol

  181. * // 换个思路,填坑赋值

  182. */

  183. private function recursionSetHeaderRowsAndMargin($headerData,$row,$col,$defStyle=[]) {

  184. // $widthSize = $this->headerWidthSize;

  185. $heightSize = $this->headerHeightSize;

  186.  
  187. $startRow = $row;

  188. $startCol = $col;

  189. // echo "开始行{$startRow},开始列{$startCol}<br />";

  190.  
  191.  
  192. // $writer->markMergedCell($sheet1, $start_row=0, $start_col=0, $end_row=0, $end_col=4);

  193. // $writer->markMergedCell($sheet1, $start_row=1, $start_col=0, $end_row=2, $end_col=0);// 姓名

  194. // $writer->markMergedCell($sheet1, $start_row=1, $start_col=1, $end_row=1, $end_col=2);// 吃饭

  195. // $writer->markMergedCell($sheet1, $start_row=1, $start_col=3, $end_row=1, $end_col=4);// 考勤

  196. $content = [];//标题

  197. $style = [];//样式

  198.  
  199. foreach ($headerData as $single){

  200. // dump($single);exit;

  201. $marginCol = isset($single['marginCol'])?$single['marginCol']:1;

  202. // 合并

  203. // 是否有子节点

  204. $hasChildren = (isset($single['children']) && !empty($single['children'])) ? true:false;

  205. // 有子节点合并一行,没有子节点直接把剩下行都算上

  206. if($hasChildren){

  207. $marginRow = 1;

  208. }else{

  209. // 一共3行 当前第1行需要合并1,2两行

  210. $marginRow =$heightSize-$startRow;

  211. }

  212. // if($startRow ==1 && $startCol ==0){

  213. // }

  214.  
  215.  
  216. $style = isset($single['style'])?array_merge($defStyle,$single['style']):$defStyle;

  217. // 如果不是合并一行一列就加入合并单元格的数组,同时每个合并的单元格都加样式(主要是方便边框)

  218. if($marginCol !=1 || $marginRow != 1){

  219. //0+2-1=1 合并了0,1两个单元格 end是1

  220. $endCol = $startCol+$marginCol-1;

  221. // 当前第1行需要合并2两行,目标单元格行号1+2-1

  222. $endRow = $startRow+$marginRow-1;

  223. // if($endRow == -1){

  224. // dump($marginRow);

  225. // dump($startRow);exit;

  226. // }

  227. $this->headerMargin[] = [

  228. 'startRow'=>$startRow,

  229. 'startCol'=>$startCol,

  230. 'endRow'=>$endRow,

  231. 'endCol'=>$endCol

  232. ];

  233.  
  234. if(isset($style['border'])){

  235. $s = ['border' => $style['border']];

  236. for ($r = 0;$r<$marginRow;$r++){

  237. $row = $startRow+ $r;

  238. for($c = 0;$c<$marginCol;$c++){

  239. $col = $startCol+$c;

  240. $this->setHeaderRowCell($row,$col,'style',$s);

  241. }

  242. }

  243. }

  244.  
  245. }

  246. $this->setHeaderRowCell($startRow,$startCol,'style',$style);

  247.  
  248.  
  249. $content = isset($single['title'])?$single['title']:'';

  250. $this->setHeaderRowCell($startRow,$startCol,'content',$content);

  251.  
  252.  
  253.  
  254.  
  255. //处理子节点

  256. if($hasChildren){

  257. $this->recursionSetHeaderRowsAndMargin($single['children'],$startRow+1,$startCol,$style);

  258. }

  259. $startCol +=$marginCol;

  260.  
  261.  
  262. // $marginRow = isset($single['marginCol'])?$single['marginCol']:1;

  263. // $startRow = $row;

  264. // $startCol = $col;

  265. // dump($single);exit;

  266. }

  267. // dump($content);

  268. // dump($style);exit;

  269. // $this->headerRow[$row]['content'] =$content;

  270. // $this->headerRow[$row]['style'] = $style;

  271.  
  272.  
  273. }

  274.  
  275. //

  276. //

  277. /**

  278. * 设置表头单元格尺寸

  279. * 同事设置列的数据类型和宽度

  280. * @param $header

  281. */

  282. private function setHeaderSize(&$headerData)

  283. {

  284.  
  285. list($w, $h) = $this->recursionCalSize($headerData);

  286. $this->headerWidthSize = $w;

  287. $this->headerHeightSize = $h;

  288. $this->setHeaderRow($w,$h);

  289.  
  290.  
  291. }

  292.  
  293. /**

  294. * 初始化表头行,占坑

  295. * @param $w

  296. * @param $h

  297. */

  298. private function setHeaderRow($w,$h){

  299. $data = [];

  300. $content = [];

  301. $style = [];

  302. while (true){

  303. $w--;

  304. $content[] = '';

  305. $style[] =[];

  306. if($w<=0) break;

  307. }

  308.  
  309. while (true){

  310. $h--;

  311. $data[] = ['content'=>$content,'style'=>$style];

  312.  
  313. if($h<=0)

  314. break;

  315. }

  316.  
  317. $this->headerRow = $data;

  318. unset($content);

  319. unset($style);

  320. unset($data);

  321.  
  322. }

  323.  
  324. /**

  325. * 修改表头行的数据

  326. * @param $col

  327. * @param $row

  328. * @param $filed

  329. * @param $value

  330. */

  331. private function setHeaderRowCell($row,$col,$key,$value){

  332.  
  333. if(isset($this->headerRow[$row][$key][$col])){

  334. $this->headerRow[$row][$key][$col] = $value;

  335. }else{

  336. return false;

  337. }

  338. }

  339. /**

  340. *

  341. * @param $headerData

  342. * @return array

  343. */

  344. private function recursionCalSize(&$headerData)

  345. {

  346. $w = 0; // 根节点++

  347. $h = 0; // 是子类高度的最大值

  348. $childHeightArr = [];

  349. foreach ($headerData as &$single) {

  350. $singleH = 1;

  351. // 没有子节点设置为空数组

  352. // $children = (isset($single['children']) && !empty($single['children'])) ? $single['children'] : [];

  353.  
  354. // if ($children) {

  355. if((isset($single['children']) && !empty($single['children']))){

  356. list($cw, $ch) = $this->recursionCalSize($single['children'], $h);

  357. // dump($h);

  358. // dump($ch);

  359. $w += $cw;

  360. $singleH += $ch;

  361. $single['marginCol'] = $cw;

  362. // echo $single['title'].":".$h;

  363.  
  364. } else {

  365.  
  366. //设置表头需要要参数

  367. //字段类型

  368. $type = isset($single['format'])?$single['format']:$this->colDefalutFormat;

  369. // 字段宽度

  370. $width = isset($single['width'])?$single['width']:$this->colDefalutWidth;

  371. $this->header[] = $type;

  372. $this->headerWidth[] = $width;

  373. // data的键

  374. $field = isset($single['field'])?$single['field']:'';

  375. $this->dataFields[] = $field;

  376.  
  377. $w ++;

  378. $singleH = 1;

  379. }

  380. $childHeightArr[] = $singleH;

  381. // $childWithArr[] = $singleW;

  382.  
  383. // if ($single['title'] == '总表头') {

  384. // dump($childHeightArr);

  385. // }

  386. }

  387. // $w = array_sum($childWithArr);

  388. $h = max($childHeightArr);

  389. return [$w, $h];

  390. }

  391. }

3.使用示例

 
  1. <?php

  2. /**

  3. * 表头配置为一个数组

  4. * 根节点参数

  5. * title 必填,表头的内容

  6. * field 根节点列的内容要显示的data字段,默认为空[则这一列不显示数据]

  7. * width 根节点的列宽,默认为10

  8. * format 根节点的列在excel中的显示类型,默认为string,其他的我没有测试,因为我实际的应用过程中,String就够用了,如果要使用其他类型,请参照xlsxwriter示例自行修改测试

  9. * format和width的默认值可以再tool里面修改

  10. * colDefalutFormat = 'string';// 列默认数据格式

  11. * colDefalutWidth = '10';// 列默认宽度

  12. *

  13. * 一般节点的参数

  14. * title 必填,表头的内容

  15. * style=> 应用的样式,会应用到根节点

  16. * 默样式可以修改headerDefalutStyle的配置

  17. * children 必填,子节点

  18. */

  19. $headerConfig = [

  20. [

  21. 'title' => '总表头',

  22. 'style'=> ['fill'=>'#33ccff'],

  23. 'children' => [

  24. ['title' => '姓名', 'field' => 'name','format'=>'string','width'=>'40'],//根节点

  25. // ['title' => '姓名', 'field' => 'name','width'=>'40'],//根节点

  26. // 一般节点

  27. [

  28. 'title' => '吃饭',

  29. 'children' => [

  30. ['title' => '上午', 'field' => 'eatam'],

  31. ['title' => '上午', 'field' => 'eatpm', 'width' => '40'],

  32. ]

  33. ],

  34. [

  35. 'title' => '工作',

  36. 'children' => [

  37. ['title' => '上午', 'field' => 'workam'],

  38. ['title' => '下午', 'field' => 'workpm'],

  39. ]

  40. ],

  41. [

  42. 'title' => '喝水',

  43. 'children' => [

  44. ['title' => '上午','format'=>'string','width'=>'40'],

  45. ['title' => '下午'],

  46. ]

  47. ],

  48. ]

  49. ]

  50. ];

  51. $data = [

  52. ['name' => '张三', 'eatam' => '8:23', 'workam' => '9:23', 'workpm' => '9:23'],

  53. ['name' => '李四', 'eatam' => '8:24', 'workam' => '9:24', 'workpm' => '9:24'],

  54. ];

  55.  
  56. include_once("../extend/PHP_XLSXWriter-master/XLSXWriterMultiHeaderTool.php");

  57. $tool = new \XLSXWriterMultiHeaderTool();

  58. $tool->setHeader($headerConfig);

  59. $tool->setData($data);

  60. $filename = date('_YmdHis');

  61. $tool->writeToStdOut($filename);

4.效果

因为data里面没有对应的字段,或者节点设置的时候没有指定的field,所以有几列是空的,表头的颜色是因为写了style的fill属性

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值