因工作需求,需要将前端页面显示的 table 报表打印成 Excel 文件,因为报表类型多样,好多的 td 标签存在跨行又跨列的情况,PHPExcel 作为一款 php 处理 Excel 的插件,虽然方便,但在处理这些报表起来也非常的复杂。
如果我们能够在 js 中确定整个 table 的行数和列数,并确定每个 td 在 Excel 中的坐标以及跨行跨列后最终点的坐标,然后在后台 php 将接收到的数据打印至 Excel,这样不仅能够减缓服务端压力,在前端的调用中也将为我们带来便利。
前台用到了 jQuery,获取 table 数据及 td 坐标的数据操作可以封装成一个插件并指定后台接口。jQ 代码如下:
(function($){
$.fn.extend({
forExcel:function(){
var list = [];// 傳入後台的數據
var span_all = []; // 記錄既跨行又跨列的坐標
var max_td = []; // 記錄最大列數
var height_tr = []; // 記錄行高
// 行 tr 循環
$(this).children().find("tr").each(function(i, dom){
// 獲取每一行的高度
height_tr.push($(dom).height()*0.75); // 像素轉換
// 記錄最大列數
max_td.push($(dom).children("td").length);
// 列 td 循環
$(dom).children("td").each(function(isub, domsub){
var tdata = {};
// 獲取跨行,跨列
var col = $(domsub).attr("colspan");
col = col ? parseInt(col) : 0;
var row = $(domsub).attr("rowspan");
row = row ? parseInt(row) : 0;
// 圖片判斷 ------ PHPExcel只允许本地图片地址 -----------------
if($(domsub).find("img").length != 0){
tdata.img = $(domsub).find("img").length;
tdata.img_src = $(domsub).find("img").attr('src'); // 图片地址需加前缀
tdata.img_width = $(domsub).find("img").width();
tdata.img_height = $(domsub).find("img").height();
}else{
tdata.img = "none";
}
// 記錄跨行坐標
if(row>0){
span_all.push({row:i, col:isub, rowspan:row, colspan:col});
}
// 獲取 td 在 Excel 中的位置
var data = getIdx(i, isub, span_all, this, row, col);
tdata.idx = data.idx;
tdata.span = data.span;
tdata.text = $(domsub).text();
list.push(tdata);
});
});
var tdata = {};
tdata.tdlength = Math.max.apply(null, max_td);
tdata.height_tr = height_tr;
var countr = $(this).children().find("tr").length;
var trdom = $(this).children().find("td").last();
var rows = $(trdom).attr("rowspan");
rows = rows ? parseInt(rows) : 1;
tdata.trlength = countr+rows-1;
tdata.tdData = list;
var tableData = JSON.stringify(tdata);
// 後台接口
$(this).after("<form id='forexcelform' action='後台接口' method='post'></form>");
$("#forexcelform").html("<input type='hidden' name=tbdata value='"+ tableData +"' />");
$("#forexcelform").submit();
$("#forexcelform").remove();
}
});
// 返回列名
function getColIdx(idx){
var col = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z', 'AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM','AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ', 'BA','BB','BC','BD','BE','BF','BG','BH','BI','BJ','BK','BL','BM','BN','BO','BP','BQ','BR','BS','BT','BU','BV','BW','BX','BY','BZ', 'CA','CB','CC','CD','CE','CF','CG','CH','CI','CJ','CK','CL','CM','CN','CO','CP','CQ','CR','CS','CT','CU','CV','CW','CX','CY','CZ', 'DA','DB','DC','DD','DE','DF','DG','DH','DI','DJ','DK','DL','DM','DN','DO','DP','DQ','DR','DS','DT','DU','DV','DW','DX','DY','DZ'];
return col[idx];
}
// 返回坐標與跨行或跨列后的坐標
function getIdx(row, col, span_all, dom, span_row, span_col){
var pre_colspan = 0; //
// 是否位於跨行跨列的行數中
$.each(span_all, function(i, val){
var rows = span_all[i].row + span_all[i].rowspan;
if(row>span_all[i].row && row<rows && col>=span_all[i].col){
pre_colspan += parseInt(span_all[i].colspan==0?1:span_all[i].colspan);
}
});
var tr_colspan = 0;
// 計算同行 td 前的 colspan
$(dom).prevAll().each(function(i, domsub){
var colspan = $(domsub).attr("colspan");
colspan = colspan ? colspan : 1;
tr_colspan += parseInt(colspan)-1;
});
pre_colspan += tr_colspan;
// 記錄開始坐標
var idx = getColIdx(col+pre_colspan) +""+ (row+1);
// 記錄跨后終點坐標
var span = "none";
// 跨行或跨列后的坐標
if(span_row>0 && span_col==0){
span = getColIdx(col+pre_colspan) +""+ (span_row+row);
}else if(span_row==0 && span_col>0){
span = getColIdx(span_col+col+pre_colspan-1) +""+ (1+row);
}else if(span_row>0 && span_col>0){
span = getColIdx(span_col+col+pre_colspan-1) +""+ (span_row+row);
}
return {idx: idx, span: span};
}
})(jQuery);
该插件获取如下图的请求参数为
{"tdlength":3,"height_tr":[16.5,16.5,16.5],"width_td":15.76,"trlength":3,"tdData":[{"idx":"A1","span":"A2","text":"eq(跨2行)","img":"none"},{"idx":"B1","span":"none","text":" eq","img":"none"},{"idx":"C1","span":"none","text":"eq","img":"none"},{"idx":"B2","span":"none","text":"eq","img":"none"},{"idx":"C2","span":"none","text":"eq","img":"none"},{"idx":"A3","span":"B3","text":"eq(跨2列)","img":"none"},{"idx":"C3","span":"none","text":"eq","img":"none"}]}
而在后台 php 接口中,核心代码如下
public function x_xPrintExcel(){
vendor("PHPExcel.PHPExcel"); // thinkPHP 加载插件
$objPHPExcel = new PHPExcel();
// 设置文件的一些属性,在xls文件——>属性——>详细信息里可以看到这些值,xml表格里是没有这些值的
$objPHPExcel
->getProperties() //获得文件属性对象,给下文提供设置资源
->setCreator( "") //设置文件的创建者
->setLastModifiedBy( "") //设置最后修改者
->setTitle( "Office 2007 XLSX Test Document" ) //设置标题
->setSubject( "Office 2007 XLSX Test Document" ) //设置主题
->setDescription( "Test document for Office 2007 XLSX, generated using PHP classes.") //设置备注
->setKeywords( "office 2007 openxml php") //设置标记
->setCategory( "Test result file"); //设置类别
$tbdata = $_POST['tbdata'];
$a = str_replace('\\','',$tbdata); // 前台傳入 json 字符串
$_tdata = json_decode($a);
$tbdata = $_tdata->tdData;
//dump($tbdata);die();
for($i=0; $i<count($tbdata); $i++){
$idx = $tbdata[$i]->idx;
$text = $tbdata[$i]->text;
$span = $tbdata[$i]->span;
$img = $tbdata[$i]->img;
$img_src = $tbdata[$i]->img_src;
$img_width = $tbdata[$i]->img_width;
$img_height = $tbdata[$i]->img_height;
if("none" != $img){
/*实例化插入图片类*/
$objDrawing = new PHPExcel_Worksheet_Drawing();
$objDrawing->setResizeProportional(false);
$objDrawing->setPath($img_src);
$objDrawing->setWidth($img_width);
$objDrawing->setHeight($img_height);
$objDrawing->setCoordinates($idx);//单元格
$objDrawing->setWorksheet($objPHPExcel->setActiveSheetIndex(0));//$sheet为当前工作表
}else{
$objPHPExcel->setActiveSheetIndex(0) //设置第一个内置表(一个xls文件里可以有多个表)为活动的
->setCellValue( $idx, $text );//setWrapText
}
// 垂直居中
$objPHPExcel->getActiveSheet()->getStyle($idx)->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_CENTER);
//自動換行
$objPHPExcel->getActiveSheet()->getStyle($idx)->getAlignment()->setWrapText(true);
if("none" != $span){
$objPHPExcel->getActiveSheet()->mergeCells( $idx.':'.$span);
}
}
// 確定行列
$tdlength = $_tdata->tdlength;
$trlength = $_tdata->trlength;
$cellName = array('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM','AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ',
'BA','BB','BC','BD','BE','BF','BG','BH','BI','BJ','BK','BL','BM','BN','BO','BP','BQ','BR','BS','BT','BU','BV','BW','BX','BY','BZ',
'CA','CB','CC','CD','CE','CF','CG','CH','CI','CJ','CK','CL','CM','CN','CO','CP','CQ','CR','CS','CT','CU','CV','CW','CX','CY','CZ',
'DA','DB','DC','DD','DE','DF','DG','DH','DI','DJ','DK','DL','DM','DN','DO','DP','DQ','DR','DS','DT','DU','DV','DW','DX','DY','DZ');
$height_tr = $_tdata->height_tr; // 行高
// 設置邊框
for($i=0; $i<$trlength; $i++){
// 行高
$objPHPExcel->getActiveSheet()->getRowDimension(''.($i+1).'')->setRowHeight($height_tr[$i]);
for($j=0; $j<$tdlength; $j++){
$idx = $cellName[$j].''.($i+1) ;
$objPHPExcel->getActiveSheet()->getStyle($idx)->getBorders()->getTop()->setBorderStyle(PHPExcel_Style_Border::BORDER_THIN);
$objPHPExcel->getActiveSheet()->getStyle($idx)->getBorders()->getBottom()->setBorderStyle(PHPExcel_Style_Border::BORDER_THIN);
$objPHPExcel->getActiveSheet()->getStyle($idx)->getBorders()->getLeft()->setBorderStyle(PHPExcel_Style_Border::BORDER_THIN);
$objPHPExcel->getActiveSheet()->getStyle($idx)->getBorders()->getRight()->setBorderStyle(PHPExcel_Style_Border::BORDER_THIN);
}
}
//
for($j=0; $j<$tdlength; $j++){
$objPHPExcel->getActiveSheet()->getColumnDimension($cellName[$j])->setWidth(15);
}
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="simple.xls"');
header('Cache-Control: max-age=0');
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5');
$objWriter->save('php://output');
exit;
}
效果如下: