最近公司业务有一些需要导出多级表头的业务需求,百度之后,没有很好的应用demo,然后接触了XLSXWriter,就想着一劳永逸,写个工具类,示例只有三级,但是不止于三级
应用举例
比如说我们需要这样一个表头
image
1.XLSXWriter应用分析
根据XLSXWriter工具类的示例
我们需要插入三行数据作为表头
image然后再合并掉对应的单元格
image
2.工具类编写
调试的注释啥的,都在里面,有兴趣的自己可以看一下。需要放在和xlsxwriter.class.php同一目录下使用
<?php
require_once 'web/libraries/XLSXwriter.php';
class PHPExeclCore
{
private $headerData = array();
private $headerWidthSize = 0;
private $headerHeightSize = 0;
private $colDefalutFormat = 'string';// 列默认数据格式
private $colDefalutWidth = '10';// 列默认宽度
private $header = array();
private $headerWidth = array();
// 数据字段
private $dataFields = array();
// 标题行默认样式
private $headerDefalutStyle = array(
'halign' => 'center',//水平居中
'valign' => 'center',//竖直居中
'font-style' => 'bold',
'font-size' => 12,
'border' => 'top,bottom,left,right'
//'fill'=>'#fff'// 背景色
// 边框有点问题不能用
// 高度也不行 用font-size撑开);
);
// 标题行处理数组
/**
* $headerRow = array(
* array('content' =>['Merge Cells Example','','','',''],'style'=>[['halign'=>'center'],array(),array(),array(),array()]),
* array('content' =>['姓名', '吃饭', '', '考勤', ''],'style'=>[['halign'=>'center','valign'=>'center'],['halign'=>'center'],[],['halign'=>'center'],[]]),
* array('content' =>['', '上午', '下午', '上午', '下午'],'style'=>[[],['halign'=>'center'],[],[],[]]),
* );
*/
private $headerRow = array(0 => array('content' => array(), 'style' => array()));
private $headerMargin = array();
// 数据行
private $dataRow = array();
// 现在够用了,先不做了
private $dataStyle = array();
public function __construct()
{
}
public function __get($name)
{
if (isset($this->$name)) {
return $this->$name;
} else {
return null;
}
// TODO: Implement __get() method.
}
/**
* 设置表头
* @param array $head
*/
public function setHeader($header = array())
{
$this->headerData = $header;
// 设置表头尺寸
$this->setHeaderSize($this->headerData);
// 生成rows 和 margin 设置样式
$this->dealHeader($this->headerData);
}
public function setData($data = array())
{
foreach ($data as $d) {
$row = array();
foreach ($this->dataFields as $field) {
$value = isset($d[$field]) ? $d[$field] : '';
$row[] = $value;
}
$this->dataRow[] = $row;
}
}
/**
* 导出
* @param string $filename
* @param string $sheet
*/
public function writeToStdOut($filename = 'test', $sheet = 'Sheet1')
{
//$header = ['字段','标题','宽度','类型']
$filename .= '.xlsx';
$writer = new \XLSXWriter();
$writer->writeSheetHeader($sheet, $this->header, $col_options = array('suppress_row' => true, 'widths' => $this->headerWidth));
foreach ($this->headerRow as $row)
$writer->writeSheetRow($sheet, $row['content'], $row['style']);
foreach ($this->headerMargin as $margin) {
$writer->markMergedCell($sheet, $margin['startRow'], $margin['startCol'], $margin['endRow'], $margin['endCol']);
}
foreach ($this->dataRow as $data)
$writer->writeSheetRow($sheet, $data);
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');
$writer->writeToStdOut();
}
/**
* 生成rows 和 margin 设置样式
* @param $headerData
*/
private function dealHeader($headerData)
{
$this->recursionSetHeaderRowsAndMargin($headerData, 0, 0, $this->headerDefalutStyle);
}
/**
* 递归设置表头行数据,和合并表格数据源
* @param $headerData
* @param $row
* @param $startCol
* // 换个思路,填坑赋值
*/
private function recursionSetHeaderRowsAndMargin($headerData, $row, $col, $defStyle = array())
{
$heightSize = $this->headerHeightSize;
$startRow = $row;
$startCol = $col;
$content = array();//标题
$style = array();//样式
foreach ($headerData as $single) {
$marginCol = isset($single['marginCol']) ? $single['marginCol'] : 1;
// 合并
// 是否有子节点
$hasChildren = (isset($single['children']) && !empty($single['children'])) ? true : false;
// 有子节点合并一行,没有子节点直接把剩下行都算上
if ($hasChildren) {
$marginRow = 1;
} else {
// 一共3行 当前第1行需要合并1,2两行
$marginRow = $heightSize - $startRow;
}
$style = isset($single['style']) ? array_merge($defStyle, $single['style']) : $defStyle;
// 如果不是合并一行一列就加入合并单元格的数组,同时每个合并的单元格都加样式(主要是方便边框)
if ($marginCol != 1 || $marginRow != 1) {
//0+2-1=1 合并了0,1两个单元格 end是1
$endCol = $startCol + $marginCol - 1;
// 当前第1行需要合并2两行,目标单元格行号1+2-1
$endRow = $startRow + $marginRow - 1;
$this->headerMargin[] = array(
'startRow' => $startRow,
'startCol' => $startCol,
'endRow' => $endRow,
'endCol' => $endCol);
if (isset($style['border'])) {
$s = array('border' => $style['border']);
for ($r = 0; $r < $marginRow; $r++) {
$row = $startRow + $r;
for ($c = 0; $c < $marginCol; $c++) {
$col = $startCol + $c;
$this->setHeaderRowCell($row, $col, 'style', $s);
}
}
}
}
$this->setHeaderRowCell($startRow, $startCol, 'style', $style);
$content = isset($single['title']) ? $single['title'] : '';
$this->setHeaderRowCell($startRow, $startCol, 'content', $content);
//处理子节点
if ($hasChildren) {
$this->recursionSetHeaderRowsAndMargin($single['children'], $startRow + 1, $startCol, $style);
}
$startCol += $marginCol;
}
}
//
//
/**
* 设置表头单元格尺寸
* 同事设置列的数据类型和宽度
* @param $header
*/
private function setHeaderSize(&$headerData)
{
list($w, $h) = $this->recursionCalSize($headerData);
$this->headerWidthSize = $w;
$this->headerHeightSize = $h;
$this->setHeaderRow($w, $h);
}
/**
* 初始化表头行,占坑
* @param $w
* @param $h
*/
private function setHeaderRow($w, $h)
{
$data = array();
$content = array();
$style = array();
while (true) {
$w--;
$content[] = '';
$style[] = array();
if ($w <= 0) break;
}
while (true) {
$h--;
$data[] = array('content' => $content, 'style' => $style);
if ($h <= 0)
break;
}
$this->headerRow = $data;
unset($content);
unset($style);
unset($data);
}
/**
* 修改表头行的数据
* @param $col
* @param $row
* @param $filed
* @param $value
*/
private function setHeaderRowCell($row, $col, $key, $value)
{
if (isset($this->headerRow[$row][$key][$col])) {
$this->headerRow[$row][$key][$col] = $value;
} else {
return false;
}
}
/**
*
* @param $headerData
* @return array
*/
private function recursionCalSize(&$headerData)
{
$w = 0; // 根节点++
$h = 0; // 是子类高度的最大值
$childHeightArr = array();
foreach ($headerData as &$single) {
$singleH = 1;
// 没有子节点设置为空数组
if ((isset($single['children']) && !empty($single['children']))) {
list($cw, $ch) = $this->recursionCalSize($single['children'], $h);
$w += $cw;
$singleH += $ch;
$single['marginCol'] = $cw;
} else {
//设置表头需要要参数
//字段类型
$type = isset($single['format']) ? $single['format'] : $this->colDefalutFormat;
// 字段宽度
$width = isset($single['width']) ? $single['width'] : $this->colDefalutWidth;
$this->header[] = $type;
$this->headerWidth[] = $width;
// data的键
$field = isset($single['field']) ? $single['field'] : '';
$this->dataFields[] = $field;
$w++;
$singleH = 1;
}
$childHeightArr[] = $singleH;
}
$h = max($childHeightArr);
return array($w, $h);
}
}
3.使用示例
<?php
/**
* 表头配置为一个数组
* 根节点参数
* title 必填,表头的内容
* field 根节点列的内容要显示的data字段,默认为空[则这一列不显示数据]
* width 根节点的列宽,默认为10
* format 根节点的列在excel中的显示类型,默认为string,其他的我没有测试,因为我实际的应用过程中,String就够用了,如果要使用其他类型,请参照xlsxwriter示例自行修改测试
* format和width的默认值可以再tool里面修改
* colDefalutFormat = 'string';// 列默认数据格式
* colDefalutWidth = '10';// 列默认宽度
*
* 一般节点的参数
* title 必填,表头的内容
* style=> 应用的样式,会应用到根节点
* 默样式可以修改headerDefalutStyle的配置
* children 必填,子节点
*/
$headerConfig =
array(
array('title' => '存量项目', 'field' => 'project_name','format'=>'string'),//根节点
array('title' => '业态', 'field' => 'layout','format'=>'string'),//根节点
array('title' => '标签', 'field' => 'project_label','format'=>'string'),//根节点
array('title' => '委托管理公司', 'field' => 'business_area_company','format'=>'string','width'=>'20'),//根节点
// ['title' => '姓名', 'field' => 'name','width'=>'40'],//根节点
// 一般节点
array('title' => '建筑面积',
'children' => array(
array('title' => '6月30日', 'field' => 'build_area_sum_1'),
array('title' => '9月30日', 'field' => 'build_area_sum_2'),
)
),
array(
'title' => '实际收入',
'children' => array(
array('title' => '6月30日', 'field' => 'rent_income_sum_1'),
array('title' => '9月30日', 'field' => 'rent_income_sum_2'),
)
),
array('title' => '单价',
'children' => array(
array('title' => '6月30日', 'field' => 'avg_price_1'),
array('title' => '9月30日', 'field' => 'avg_price_2'),
array('title' => '差值', 'field' => 'avg_price_diff')
)
),
array('title' => '空置率(%)',
'children' => array(
array('title' => '6月30日', 'field' => 'rent_rate_1'),
array('title' => '9月30日', 'field' => 'rent_rate_2'),
array('title' => '差值', 'field' => 'rent_rate_diff')
)
)
);
$data = [
['project_name' => '张三', 'project_name' => '8:23', 'build_area_sum_1' => '9:23', 'avg_price_1' => '9:23'],
['project_name' => '李四', 'project_name' => '8:24', 'build_area_sum_1' => '9:24', 'avg_price_1' => '9:24'],
];
include_once("../extend/PHP_XLSXWriter-master/XLSXWriterMultiHeaderTool.php");
$tool = new \XLSXWriterMultiHeaderTool();
$tool->setHeader($headerConfig);
$tool->setData($data);
$filename = date('_YmdHis');
$tool->writeToStdOut($filename);
4.效果
因为data里面没有对应的字段,或者节点设置的时候没有指定的field,所以有几列是空的,表头的颜色是因为写了style的fill属性
image
网友评论