最近公司業務有一些需要導出多級表頭的業務需求,百度之后,沒有很好的應用demo,然后接觸了XLSXWriter,就想著一勞永逸,寫個工具類,示例只有三級,但是不止于三級
應用舉例
比如說我們需要這樣一個表頭
image
1.XLSXWriter應用分析
根據XLSXWriter工具類的示例
我們需要插入三行數據作為表頭
image
然后再合并掉對應的單元格
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