将页面复杂的Table导出为Excel文档
一、POI依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.8</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.8</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.8</version>
</dependency>
二、导出工具类
1、使用HSSFWorkbook
package com.demo.utils;
import com.demo.model.CrossRangeCellMeta;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.util.CellRangeAddress;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 类名:Html2Excel.java
* 路径:com.demo.utils.Html2Excel.java
* 创建人:tanyp
* 创建时间:2019/9/19 10:00
* 功能:将html table转成excel
* 修改人:
* 修改时间:
* 修改备注:
*/
public class Html2Excel {
/**
* 方法名:table2Excel
* 功能:html表格转excel
* 创建人:tanyp
* 创建时间:2019/9/19 10:00
* 入参:html字符串:<table> ... </table>
* 出参:excel
* 修改人:
* 修改时间:
* 修改备注:
*/
public static HSSFWorkbook table2Excel(String tableHtml) {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet();
HSSFCellStyle style = wb.createCellStyle();
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
List<CrossRangeCellMeta> crossRowEleMetaLs = new ArrayList<>();
int rowIndex = 0;
try {
Document data = DocumentHelper.parseText(tableHtml);
// 生成表头
Element thead = data.getRootElement().element("thead");
HSSFCellStyle titleStyle = getTitleStyle(wb);
if (thead != null) {
List<Element> trLs = thead.elements("tr");
for (Element trEle : trLs) {
HSSFRow row = sheet.createRow(rowIndex);
List<Element> thLs = trEle.elements("th");
makeRowCell(thLs, rowIndex, row, 0, titleStyle, crossRowEleMetaLs);
row.setHeightInPoints(17);
rowIndex++;
}
}
// 生成表体
Element tbody = data.getRootElement().element("tbody");
if (tbody != null) {
HSSFCellStyle contentStyle = getContentStyle(wb);
List<Element> trLs = tbody.elements("tr");
for (Element trEle : trLs) {
HSSFRow row = sheet.createRow(rowIndex);
List<Element> thLs = trEle.elements("th");
int cellIndex = makeRowCell(thLs, rowIndex, row, 0, titleStyle, crossRowEleMetaLs);
List<Element> tdLs = trEle.elements("td");
makeRowCell(tdLs, rowIndex, row, cellIndex, contentStyle, crossRowEleMetaLs);
row.setHeightInPoints(18);
rowIndex++;
}
}
// 合并表头
for (CrossRangeCellMeta crcm : crossRowEleMetaLs) {
sheet.addMergedRegion(new CellRangeAddress(crcm.getFirstRow(), crcm.getLastRow(), crcm.getFirstCol(), crcm.getLastCol()));
}
} catch (DocumentException e) {
e.printStackTrace();
}
//自动调整列宽
for (int i = 0; i < 15; i++) {
sheet.autoSizeColumn((short) i);
}
return wb;
}
/**
* 方法名:makeRowCell
* 功能:生产行内容
* 创建人:tanyp
* 创建时间:2019/9/19 10:01
* 入参:
* tdLs: th或者td集合;
* rowIndex: 行号;
* row: POI行对象;
* startCellIndex;
* cellStyle: 样式;
* crossRowEleMetaLs:跨行元数据集合
* 出参:
* 修改人:
* 修改时间:
* 修改备注:
*/
private static int makeRowCell(List<Element> tdLs, int rowIndex, HSSFRow row, int startCellIndex, HSSFCellStyle cellStyle, List<CrossRangeCellMeta> crossRowEleMetaLs) {
int i = startCellIndex;
for (int eleIndex = 0; eleIndex < tdLs.size(); i++, eleIndex++) {
int captureCellSize = getCaptureCellSize(rowIndex, i, crossRowEleMetaLs);
while (captureCellSize > 0) {
// 当前行跨列处理(补单元格)
for (int j = 0; j < captureCellSize; j++) {
row.createCell(i);
i++;
}
captureCellSize = getCaptureCellSize(rowIndex, i, crossRowEleMetaLs);
}
Element thEle = tdLs.get(eleIndex);
String val = thEle.getTextTrim();
if (StringUtils.isBlank(val)) {
Element e = thEle.element("a");
if (e != null) {
val = e.getTextTrim();
}
}
HSSFCell c = row.createCell(i);
if (NumberUtils.isNumber(val)) {
c.setCellValue(Double.parseDouble(val));
c.setCellType(HSSFCell.CELL_TYPE_NUMERIC);
} else {
c.setCellValue(val);
}
c.setCellStyle(cellStyle);
int rowSpan = NumberUtils.toInt(thEle.attributeValue("rowspan"), 1);
int colSpan = NumberUtils.toInt(thEle.attributeValue("colspan"), 1);
// 存在跨行或跨列
if (rowSpan > 1 || colSpan > 1) {
crossRowEleMetaLs.add(new CrossRangeCellMeta(rowIndex, i, rowSpan, colSpan));
}
// 当前行跨列处理(补单元格)
if (colSpan > 1) {
for (int j = 1; j < colSpan; j++) {
i++;
row.createCell(i);
}
}
}
return i;
}
/**
* 方法名:getCaptureCellSize
* 功能:获得因rowSpan占据的单元格
* 创建人:tanyp
* 创建时间:2019/9/19 10:03
* 入参:
* rowIndex:行号
* colIndex:列号
* crossRowEleMetaLs:跨行列元数据
* 出参:当前行在某列需要占据单元格
* 修改人:
* 修改时间:
* 修改备注:
*/
private static int getCaptureCellSize(int rowIndex, int colIndex, List<CrossRangeCellMeta> crossRowEleMetaLs) {
int captureCellSize = 0;
for (CrossRangeCellMeta crossRangeCellMeta : crossRowEleMetaLs) {
if (crossRangeCellMeta.getFirstRow() < rowIndex && crossRangeCellMeta.getLastRow() >= rowIndex) {
if (crossRangeCellMeta.getFirstCol() <= colIndex && crossRangeCellMeta.getLastCol() >= colIndex) {
captureCellSize = crossRangeCellMeta.getLastCol() - colIndex + 1;
}
}
}
return captureCellSize;
}
/**
* 方法名:getTitleStyle
* 功能:获得标题样式
* 创建人:tanyp
* 创建时间:2019/9/19 10:04
* 入参:workbook
* 出参:
* 修改人:
* 修改时间:
* 修改备注:
*/
private static HSSFCellStyle getTitleStyle(HSSFWorkbook workbook) {
short titlebackgroundcolor = HSSFColor.GREY_25_PERCENT.index;
short fontSize = 12;
String fontName = "宋体";
HSSFCellStyle style = workbook.createCellStyle();
style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
style.setBorderBottom((short) 1);
style.setBorderTop((short) 1);
style.setBorderLeft((short) 1);
style.setBorderRight((short) 1);
style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
// 背景色
style.setFillForegroundColor(titlebackgroundcolor);
HSSFFont font = workbook.createFont();
font.setFontName(fontName);
font.setFontHeightInPoints(fontSize);
font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
style.setFont(font);
return style;
}
/**
* 方法名:getContentStyle
* 功能:获得内容样式
* 创建人:tanyp
* 创建时间:2019/9/19 10:05
* 入参:HSSFWorkbook
* 出参:
* 修改人:
* 修改时间:
* 修改备注:
*/
private static HSSFCellStyle getContentStyle(HSSFWorkbook wb) {
short fontSize = 12;
String fontName = "宋体";
HSSFCellStyle style = wb.createCellStyle();
style.setBorderBottom((short) 1);
style.setBorderTop((short) 1);
style.setBorderLeft((short) 1);
style.setBorderRight((short) 1);
HSSFFont font = wb.createFont();
font.setFontName(fontName);
font.setFontHeightInPoints(fontSize);
style.setFont(font);
return style;
}
}
异常:使用Html2Excel.java导出时列超过256时会报错:
Invalid column index (256). Allowable column range for BIFF8 is (0..255) or ('A'..'IV')。
原因:使用HSSFWorkbook最多只能创建256列,超过就会报上述的错误。
解决方法:使用XSSFWorkbook创建,最多可以创建16384列。修改Html2Excel工具类为XHtml2Excel.java,如下:
2、使用XSSFWorkbook
package com.demo.utils;
import com.demo.model.CrossRangeCellMeta;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.*;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 类名:XHtml2Excel.java
* 路径:com.demo.utils.XHtml2Excel.java
* 创建人:tanyp
* 创建时间:2019/9/19 10:00
* 功能:将html table转成excel
* 修改人:
* 修改时间:
* 修改备注:
*/
public class XHtml2Excel {
/**
* 方法名:table2Excel
* 功能:html表格转excel
* 创建人:tanyp
* 创建时间:2019/9/19 10:00
* 入参:html字符串:<table> ... </table>
* 出参:excel
* 修改人:
* 修改时间:
* 修改备注:
*/
public static XSSFWorkbook table2Excel(String tableHtml) {
XSSFWorkbook wb = new XSSFWorkbook();
XSSFSheet sheet = wb.createSheet();
XSSFCellStyle style = wb.createCellStyle();
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
List<CrossRangeCellMeta> crossRowEleMetaLs = new ArrayList<>();
int rowIndex = 0;
try {
Document data = DocumentHelper.parseText(tableHtml);
// 生成表头
Element thead = data.getRootElement().element("thead");
XSSFCellStyle titleStyle = getTitleStyle(wb);
if (thead != null) {
List<Element> trLs = thead.elements("tr");
for (Element trEle : trLs) {
XSSFRow row = sheet.createRow(rowIndex);
List<Element> thLs = trEle.elements("th");
makeRowCell(thLs, rowIndex, row, 0, titleStyle, crossRowEleMetaLs);
row.setHeightInPoints(17);
rowIndex++;
}
}
// 生成表体
Element tbody = data.getRootElement().element("tbody");
if (tbody != null) {
XSSFCellStyle contentStyle = getContentStyle(wb);
List<Element> trLs = tbody.elements("tr");
for (Element trEle : trLs) {
XSSFRow row = sheet.createRow(rowIndex);
List<Element> thLs = trEle.elements("th");
int cellIndex = makeRowCell(thLs, rowIndex, row, 0, titleStyle, crossRowEleMetaLs);
List<Element> tdLs = trEle.elements("td");
makeRowCell(tdLs, rowIndex, row, cellIndex, contentStyle, crossRowEleMetaLs);
row.setHeightInPoints(18);
rowIndex++;
}
}
// 合并表头
for (CrossRangeCellMeta crcm : crossRowEleMetaLs) {
sheet.addMergedRegion(new CellRangeAddress(crcm.getFirstRow(), crcm.getLastRow(), crcm.getFirstCol(), crcm.getLastCol()));
}
} catch (DocumentException e) {
e.printStackTrace();
}
//自动调整列宽
for (int i = 0; i < 15; i++) {
sheet.autoSizeColumn((short) i);
}
return wb;
}
/**
* 方法名:makeRowCell
* 功能:生产行内容
* 创建人:tanyp
* 创建时间:2019/9/19 10:01
* 入参:
* tdLs: th或者td集合;
* rowIndex: 行号;
* row: POI行对象;
* startCellIndex;
* cellStyle: 样式;
* crossRowEleMetaLs:跨行元数据集合
* 出参:
* 修改人:
* 修改时间:
* 修改备注:
*/
private static int makeRowCell(List<Element> tdLs, int rowIndex, XSSFRow row, int startCellIndex, XSSFCellStyle cellStyle, List<CrossRangeCellMeta> crossRowEleMetaLs) {
int i = startCellIndex;
for (int eleIndex = 0; eleIndex < tdLs.size(); i++, eleIndex++) {
int captureCellSize = getCaptureCellSize(rowIndex, i, crossRowEleMetaLs);
while (captureCellSize > 0) {
// 当前行跨列处理(补单元格)
for (int j = 0; j < captureCellSize; j++) {
row.createCell(i);
i++;
}
captureCellSize = getCaptureCellSize(rowIndex, i, crossRowEleMetaLs);
}
Element thEle = tdLs.get(eleIndex);
String val = thEle.getTextTrim();
if (StringUtils.isBlank(val)) {
Element e = thEle.element("a");
if (e != null) {
val = e.getTextTrim();
}
}
XSSFCell c = row.createCell(i);
if (NumberUtils.isNumber(val)) {
c.setCellValue(Double.parseDouble(val));
c.setCellType(HSSFCell.CELL_TYPE_NUMERIC);
} else {
c.setCellValue(val);
}
c.setCellStyle(cellStyle);
int rowSpan = NumberUtils.toInt(thEle.attributeValue("rowspan"), 1);
int colSpan = NumberUtils.toInt(thEle.attributeValue("colspan"), 1);
// 存在跨行或跨列
if (rowSpan > 1 || colSpan > 1) {
crossRowEleMetaLs.add(new CrossRangeCellMeta(rowIndex, i, rowSpan, colSpan));
}
// 当前行跨列处理(补单元格)
if (colSpan > 1) {
for (int j = 1; j < colSpan; j++) {
i++;
row.createCell(i);
}
}
}
return i;
}
/**
* 方法名:getCaptureCellSize
* 功能:获得因rowSpan占据的单元格
* 创建人:tanyp
* 创建时间:2019/9/19 10:03
* 入参:
* rowIndex:行号
* colIndex:列号
* crossRowEleMetaLs:跨行列元数据
* 出参:当前行在某列需要占据单元格
* 修改人:
* 修改时间:
* 修改备注:
*/
private static int getCaptureCellSize(int rowIndex, int colIndex, List<CrossRangeCellMeta> crossRowEleMetaLs) {
int captureCellSize = 0;
for (CrossRangeCellMeta crossRangeCellMeta : crossRowEleMetaLs) {
if (crossRangeCellMeta.getFirstRow() < rowIndex && crossRangeCellMeta.getLastRow() >= rowIndex) {
if (crossRangeCellMeta.getFirstCol() <= colIndex && crossRangeCellMeta.getLastCol() >= colIndex) {
captureCellSize = crossRangeCellMeta.getLastCol() - colIndex + 1;
}
}
}
return captureCellSize;
}
/**
* 方法名:getTitleStyle
* 功能:获得标题样式
* 创建人:tanyp
* 创建时间:2019/9/19 10:04
* 入参:workbook
* 出参:
* 修改人:
* 修改时间:
* 修改备注:
*/
private static XSSFCellStyle getTitleStyle(XSSFWorkbook workbook) {
short titlebackgroundcolor = HSSFColor.GREY_25_PERCENT.index;
short fontSize = 12;
String fontName = "宋体";
XSSFCellStyle style = workbook.createCellStyle();
style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
style.setBorderBottom((short) 1);
style.setBorderTop((short) 1);
style.setBorderLeft((short) 1);
style.setBorderRight((short) 1);
style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
// 背景色
style.setFillForegroundColor(titlebackgroundcolor);
XSSFFont font = workbook.createFont();
font.setFontName(fontName);
font.setFontHeightInPoints(fontSize);
font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
style.setFont(font);
return style;
}
/**
* 方法名:getContentStyle
* 功能:获得内容样式
* 创建人:tanyp
* 创建时间:2019/9/19 10:05
* 入参:HSSFWorkbook
* 出参:
* 修改人:
* 修改时间:
* 修改备注:
*/
private static XSSFCellStyle getContentStyle(XSSFWorkbook wb) {
short fontSize = 12;
String fontName = "宋体";
XSSFCellStyle style = wb.createCellStyle();
style.setBorderBottom((short) 1);
style.setBorderTop((short) 1);
style.setBorderLeft((short) 1);
style.setBorderRight((short) 1);
XSSFFont font = wb.createFont();
font.setFontName(fontName);
font.setFontHeightInPoints(fontSize);
style.setFont(font);
return style;
}
}
3、跨行列实体类
package com.demo.model;
/**
* 类名:CrossRangeCellMeta.java
* 路径:com.winner.model.CrossRangeCellMeta.java
* 创建人:tanyp
* 创建时间:2019/9/19 10:00
* 功能:跨行元素元数据
* 修改人:
* 修改时间:
* 修改备注:
*/
public class CrossRangeCellMeta {
public CrossRangeCellMeta(int firstRowIndex, int firstColIndex, int rowSpan, int colSpan) {
super();
this.firstRowIndex = firstRowIndex;
this.firstColIndex = firstColIndex;
this.rowSpan = rowSpan;
this.colSpan = colSpan;
}
private int firstRowIndex;
private int firstColIndex;
// 跨越行数
private int rowSpan;
// 跨越列数
private int colSpan;
public int getFirstRow() {
return firstRowIndex;
}
public int getLastRow() {
return firstRowIndex + rowSpan - 1;
}
public int getFirstCol() {
return firstColIndex;
}
public int getLastCol() {
return firstColIndex + colSpan - 1;
}
public int getColSpan() {
return colSpan;
}
}
4、controller调用
/**
* 类名:Html2ExcelController.java
* 路径:com.demo.controller.Html2ExcelController.java
* 创建人:tanyp
* 创建时间:2019/9/19 10:00
* 功能:HTML table导出excel
* 修改人:
* 修改时间:
* 修改备注:
*/
@Controller
public class Html2ExcelController {
private static final Logger log = LoggerFactory.getLogger(Html2ExcelController.class);
/**
* 方法名:exportExcel
* 功能:导出
* 创建人:tanyp
* 创建时间:2019/9/19 11:07
* 入参:dataTable
* 出参:
* 修改人:
* 修改时间:
* 修改备注:
*/
@RequestMapping("exportExcel")
public void exportExcel(HttpServletRequest request, HttpServletResponse response) {
try {
String dataTable = request.getParameter("dataTable");
String fileName = "测试"+System.currentTimeMillis()+".xls";
// 各浏览器基本都支持ISO编码
String userAgent = request.getHeader("User-Agent").toUpperCase();
if (userAgent.contains("TRIDENT") || userAgent.contains("EDGE")) {
fileName = java.net.URLEncoder.encode(fileName, "UTF-8");
} else if (userAgent.contains("MSIE")) {
fileName = new String(fileName.getBytes(), "ISO-8859-1");
} else {
fileName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
}
response.addHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", fileName));
OutputStream os = new BufferedOutputStream(response.getOutputStream());
response.setContentType("application/vnd.ms-excel;charset=gb2312");
//将excel写入到输出流中
HSSFWorkbook workbook = Html2Excel.table2Excel(dataTable);
// XSSFWorkbook workbook = XHtml2Excel.table2Excel(dataTable);
workbook.write(os);
os.flush();
os.close();
} catch (Exception e) {
e.printStackTrace();
log.error("请求 exportExcel异常:{}", e.getMessage());
}
}
}
备注:
使用两个不同工具类时导包不同(HSSFWorkbook或XSSFWorkbook) 。
5、HTML代码
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="js/jquery.js"></script>
</head>
<body>
<div onclick="download()">导出</div>
<div id="DateTable" style="overflow-x:scroll">
<table id="myTable" class="gridtable w2000"><thead><tr><th rowspan="2" style="min-width: 100px; max-width: 100px;width: 100px;">名称</th><th rowspan="2" style="min-width: 80px; max-width: 80px;width: 80px;">客流总量(人次)</th><th rowspan="2" style="min-width: 80px; max-width: 80px;width: 80px;">编码</th><th rowspan="2" style="min-width: 80px; max-width: 80px;width: 80px;">单客流总量(人次)</th><th colspan="3">时段客流(人次)</th></tr><tr><th style="min-width: 80px; max-width: 80px;width: 80px;">01:05</th><th style="min-width: 80px; max-width: 80px;width: 80px;">01:10</th><th style="min-width: 80px; max-width: 80px;width: 80px;">01:15</th></tr></thead><tbody id="pointBasedDateTbody"><tr><td rowspan="2">购物一号门</td><td rowspan="2">5006</td><td>C001</td><td>3006</td><td>0</td><td>0</td><td>0</td></tr><tr><td>C002</td><td>2000</td><td>0</td><td>0</td><td>0</td></tr><tr><td rowspan="2">购物二号门</td><td rowspan="2">2796</td><td>C001</td><td>1542</td><td>0</td><td>0</td><td>0</td></tr><tr><td>C002</td><td>1254</td><td>0</td><td>0</td><td>0</td></tr><tr><td rowspan="1">购物三号门</td><td rowspan="1">1654</td><td>C005</td><td>1654</td><td>0</td><td>0</td><td>0</td></tr><tr><td rowspan="1">购物四号门</td><td rowspan="1">54365</td><td>C001</td><td>54365</td><td>0</td><td>0</td><td>0</td></tr></tbody></table>
</div>
<form action="exportExcel" id="exportExcle" method="POST">
<input type="hidden" name="dataTable" id="dataTable">
</form>
</body>
<script>
function download() {
var html = $("#tables").html();
$("#dataTable").val(html);
$("#exportExcle").submit();
};
</script>
</html>