poi导出word内嵌表格
- 设计分析部分
- 后端代码部分
-
- xml文件配置
- 实体类
- controller层
- 使用到的工具类
- service层
- 前端代码部分
-
- 测试页面
设计分析部分
首先要对导出word的需求进行分析,以下展示,本项目中,导出的word格式
出于某些保密原因,某些图片已被隐藏,可能给大家看到的效果没有现实导出的效果好,请各位自行脑补,多多包涵。
为什么要使用poi?其实,设计的时候,也思考过,使用模板的形式,直接向模板中添加数据,后来发现,如果使用模板非常容易产生错版,而使用poi会更好的自定义生成某一栏,自定义某一栏的行高等,所以尝试使用poi完成该项目的需求;
废话不多说,上代码:
后端代码部分
xml文件配置
引入项目相关的依赖,注意,此处的版本号常见使用3.11版本,那么,如果使用3.11版本,在本套代码中,插入图片部分的操作,需要作出部分改动
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
实体类
实体类部分,我选择的是表头的几个栏目配置为一个实体类
@Data
public class MaterialEntity {
private String id;
private Integer sort;//序号
private String specifications;//规格
private String unit;//单位
private String num;//数量
private Double netWeight;//净量
private Double total;//总量
private String remark;//备注
private String materialNo;//物码
}
controller层
controller层上,提供一个接口,供导出时,页面调用
@RequestMapping(value = "/exportDesignWord",method = RequestMethod.POST)
public void exportDesignWord(@RequestParam(value = "basicId")String basicId ,HttpServletResponse response){
try {
materialService.exportDesign(basicId,response);
}catch (Exception e){
e.printStackTrace();
}
}
使用到的工具类
package com.exportword.utils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlToken;
import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.imageio.stream.FileImageInputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigInteger;
import java.util.List;
public class ExportWord extends XWPFDocument {
public static PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
private XWPFDocument doc;
public ExportWord() {
super();
}
public ExportWord(InputStream in) throws IOException {
super(in);
}
public ExportWord(HttpServletResponse response, String fileName) {
try {
response.setContentType("application/vnd.ms-excel;charset=utf-8");
response.setHeader("Content-Disposition",
"attachment;filename=" + new String((fileName).getBytes(), "iso-8859-1"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public ExportWord(OPCPackage pkg) throws IOException {
super(pkg);
}
public XWPFDocument getDoc(){
return this.doc;
}
public void mergeCellsHorizontal(XWPFTable table, int row, int fromCell, int toCell) {
for (int cellIndex = fromCell; cellIndex <= toCell; cellIndex++) {
XWPFTableCell cell = table.getRow(row).getCell(cellIndex);
if ( cellIndex == fromCell ) {
cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART);
} else {
cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);
}
}
}
/**
* @描述: 跨行并单元格
*/
public void mergeCellsVertically(XWPFTable table, int col, int fromRow, int toRow) {
for (int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {
XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
if ( rowIndex == fromRow ) {
// The first merged cell is set with RESTART merge value
cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART);
} else {
// Cells which join (merge) the first one, are set with CONTINUE
cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.CONTINUE);
}
}
}
/**
* 合并单元格,从第几行第几列合并到第几行到第几列
*/
public void mergeCellsComplex(XWPFTable table, int fromRow, int fromCol, int toRow,int toCol) {
int rows = toRow - fromRow;
int cols = toCol - fromCol;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
mergeCellsVertically(table,fromCol+j,fromRow,toRow);
}
}
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
mergeCellsHorizontal(table,fromRow+j,fromCol,toCol);
}
}
}
/**
* @描述: 设置标题
*/
public void setTitle(XWPFParagraph titleParagraph, String title) {
titleParagraph.setAlignment(ParagraphAlignment.CENTER);
XWPFRun titleParagraphRun = titleParagraph.createRun();
titleParagraphRun.setText(title);
titleParagraphRun.setColor("000000");
titleParagraphRun.setFontSize(22);
titleParagraphRun.setUnderline(UnderlinePatterns.DOUBLE);
setFontFamily(titleParagraphRun,"黑体");
}
/**
* @描述: 设置字体
*/
public void setFontFamily(XWPFRun run,String fontFamily) {
CTRPr rpr = run.getCTR().getRPr();
CTFonts fonts = rpr.addNewRFonts();
fonts.setAscii(fontFamily);
fonts.setEastAsia(fontFamily);
fonts.setHAnsi(fontFamily);
}
/**
* @描述: 换行
*/
public void getWrap(ExportWord document) {
XWPFParagraph paragraph = document.createParagraph();
XWPFRun paragraphRun = paragraph.createRun();
// paragraphRun.addBreak();
}
/**
* createTable
*/
public XWPFTable createTable(ExportWord document, int rows, int cols) {
XWPFTable infoTable = document.createTable(rows, cols);
CTTbl ttbl = infoTable.getCTTbl();
CTTblPr tblPr = ttbl.getTblPr() == null ? ttbl.addNewTblPr() : ttbl.getTblPr();
CTTblWidth tblWidth = tblPr.isSetTblW() ? tblPr.getTblW() : tblPr.addNewTblW();
CTJc cTJc = tblPr.addNewJc();
cTJc.setVal(STJc.Enum.forString("center"));
tblWidth.setW(new BigInteger("8300"));
tblWidth.setType(STTblWidth.DXA);
return infoTable;
}
/**
* @描述: 设置列宽
*/
public void setColWidth(XWPFTable table) {
List<XWPFTableRow> rows = table.getRows();
XWPFTableRow row;
List<XWPFTableCell> cells;
XWPFTableCell cell;
int rowSize = rows.size();
int cellSize;
for (int i = 0; i < rowSize; i++) {
row = rows.get(i);
cells = row.getTableCells(); // 获得单元格
cellSize = cells.size();
// 分列设置单元格宽度或者说列宽
row.setHeight(400); // 设置行高
for (int j = 0; j < cellSize; j++) {
cell = cells.get(j);
CTTcPr cellPr = cell.getCTTc().addNewTcPr();
CTTblWidth cellw = cellPr.addNewTcW();
cellw.setType(STTblWidth.DXA);
cellPr.addNewTcW().setW(BigInteger.valueOf(500));
}
}
}
/**
* @描述: 获取表格的列(不包括合并单元格的)
*/
public int getCellOfRow(XWPFTable table){
List<XWPFTableRow> rows = table.getRows();
if(rows.size()>0){
return rows.get(0).getTableCells().size();
}else{
return 0;
}
}
/**
* @描述: 给表格赋值
*/
public void setTableTextLeft(XWPFTable table,int row,int col,String msg) {
setTableText(table,row,col,msg,ParagraphAlignment.LEFT);
}
public void setTableTextRight(XWPFTable table,int row,int col,String msg) {
setTableText(table,row,col,msg,ParagraphAlignment.RIGHT);
}
public void setTableTextCenter(XWPFTable table,int row,int col,String msg) {
setTableText(table,row,col,msg,ParagraphAlignment.CENTER);
}
public void setTableTextBoth(XWPFTable table,int row,int col,String msg) {
setTableText(table,row,col,msg,ParagraphAlignment.BOTH);
}
public void setTableText(XWPFTable table,int row,int col,String msg,ParagraphAlignment alignment) {
List<XWPFTableCell> tableCells = table.getRow(row).getTableCells();
XWPFTableCell cell= tableCells.get(col);
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
XWPFParagraph table_p = cell.getParagraphs().get(0);
table_p.setAlignment(alignment);
if (msg.indexOf("\n")>0){
String[] split = msg.split("\n");
for (int i = 0; i < split.length; i++) {
XWPFRun table_run= table_p.createRun();
table_run.setText(split[i]);
table_run.setColor("000000");
table_run.setFontSize(10);
if (i<split.length-1){
table_run.addBreak();//换行
}
setFontFamily(table_run,"宋体");
}
}else {
XWPFRun table_run= table_p.createRun();
table_run.setText(msg);
table_run.setColor("000000");
table_run.setFontSize(10);
setFontFamily(table_run,"宋体");
}
}
public void setTableTextAndImg(XWPFTable table,int row,int col,String picName,int imgWidth,int imgHeight,String msg) {
List<XWPFTableCell> tableCells = table.getRow(row).getTableCells();
XWPFTableCell cell= tableCells.get(col);
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
XWPFParagraph table_p = cell.getParagraphs().get(0);
table_p.setAlignment(ParagraphAlignment.LEFT);
try {
InputStream inputStream = pathMatchingResourcePatternResolver.getResource("static/images/" + picName).getInputStream();
table_p.createRun().addPicture(inputStream, XWPFDocument.PICTURE_TYPE_PNG, picName, Units.toEMU(imgWidth), Units.toEMU(imgHeight));
} catch (InvalidFormatException | IOException e) {
e.printStackTrace();
}
XWPFRun table_run = table_p.createRun();
if (msg.indexOf("\n")>0){
String[] split = msg.split("\n");
for (int i = 0; i < split.length; i++) {
table_run.setText(split[i]);
table_run.setColor("000000");
table_run.setFontSize(10);
if (i<split.length-1){
table_run.addBreak();//换行
}
setFontFamily(table_run,"宋体");
}
}else {
table_run.setText(msg);
table_run.setColor("000000");
table_run.setFontSize(10);
setFontFamily(table_run,"宋体");
}
}
/**
* setTableText
* @描述: 给表格赋值并且设置样式
*/
public void setTableText(XWPFTable table,int row,int col,String msg, String style) {
List<XWPFTableCell> tableCells = table.getRow(row).getTableCells();
XWPFTableCell cell= tableCells.get(col);
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
XWPFParagraph table_p = cell.getParagraphs().get(0);
table_p.setAlignment(ParagraphAlignment.CENTER);
XWPFRun table_run= table_p.createRun();
table_run.setText(msg);
table_run.setColor("000000");
table_run.setFontSize(10);
setFontFamily(table_run,"宋体");
if("bold".equals(style)){
table_run.setBold(true);
}
if("boldBig".equals(style)){
table_run.setFontSize(14);
table_run.setBold(true);
}
}
/**
*
* image2byte
* @描述: 将图片转换成字节保存到byte数组
*/
public byte[] image2byte(String path) {
byte[] data = null;
FileImageInputStream input = null;
try {
input = new FileImageInputStream(new File(path));
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int numBytesRead = 0;
while ((numBytesRead = input.read(buf)) != -1) {
output.write(buf, 0, numBytesRead);
}
data = output.toByteArray();
output.close();
input.close();
} catch (FileNotFoundException ex1) {
ex1.printStackTrace();
} catch (IOException ex1) {
ex1.printStackTrace();
}
return data;
}
}
service层
service层其实是一个绘制表格的过程,以及遍历数据list,用数据填充表格的过程
@Service
public class MaterialService {
public void exportDesign(String name, HttpServletResponse response) {
//此处是查询出的实体类型list,用来遍历填充表单内容
List<MaterialEntity> listQuery = findListQuery(19);
//使用填充的数据为假数据,到正式使用时,请自行写接口获取到一个list
exportWord(response,listQuery);
}
//此处为造假数据的方法
private List<MaterialEntity> findListQuery(int dataTolRows) {
List<MaterialEntity> resultList = new ArrayList<MaterialEntity>();
for (int i = 0; i < dataTolRows; i++) {
MaterialEntity materialEntity = new MaterialEntity();
materialEntity.setId(UUID.randomUUID().toString());
materialEntity.setSort(i+1);
materialEntity.setSpecifications("无缝钢管 Sch160 PE Φ21.3×4.78 GB/T 6479-20"+i);
materialEntity.setUnit("t");
materialEntity.setNetWeight((double) (((int)(Math.random()*10000))/100));
materialEntity.setTotal((double) (((int)(Math.random()*10000))/100));
materialEntity.setRemark("ABCD"+"-"+i);
resultList.add(materialEntity);
}
return resultList;
}
//正式开始导出数据
private void exportWord(HttpServletResponse response, List<MaterialEntity> listQuery) {
System.out.println(listQuery);
OutputStream out = null;
try {
// 获取输出流
out = response.getOutputStream();
//start创建表格部分开始
// Word名字 后缀必须输docx
ExportWord document = new ExportWord(response,"文件名.docx");
//根据页面设计的格式,填入数据时只填入的19行数据
//除19行数据外,2行为表最底端空余的两行,并插入的说明内容
//7行为表头及上方合并的单元格大块,所以,共创建19+9=28行表格;
int rows = listQuery.size() + 9;//7+2 19+9=28 建立28行12列的表
//12列,可以参照表格最下方的一行表格,上方表格多少都有单元格合并,无法直观看出12行单元格
XWPFTable table_base= document.createTable(document, rows,12);
//end创建表格部分结束
//start此处开始,进行单元格合并,合并成导出文件时,所需的表格样式
//设置表头信息
document.mergeCellsComplex(table_base,0,0,6,2);
document.mergeCellsComplex(table_base,0,3,2,7);
document.mergeCellsComplex(table_base,2,3,6,7);
document.mergeCellsHorizontal(table_base,0,8,11);
document.mergeCellsHorizontal(table_base,1,8,11);
document.mergeCellsHorizontal(table_base,2,8,11);
document.mergeCellsHorizontal(table_base,3,8,11);
document.mergeCellsHorizontal(table_base,4,8,11);
document.mergeCellsHorizontal(table_base,5,8,10);
//设置实体数据信息
document.mergeCellsHorizontal(table_base,6,1,5);
document.mergeCellsHorizontal(table_base,6,10,11);
//end单元格合并结束,此时的表格基本与导出表格的外形相同
//start此处开始,进行单元格部分数据填充
//首先填充上方合并的大单元格部分
//shiyou1.png为本地存放的一张图片,用于插入表中,作为页面上方的logo显示(已打马部分)
document.setTableTextAndImg(table_base,0,0,"shiyou1.png",130,40," \n工程设计证书:A13\n" +
" 924\n工程:150004-key\n 专业 HG ");
document.setTableTextCenter(table_base,0,3,"材料表");
document.setTableTextCenter(table_base,2,3,"AAA\n改(成本部分)\n");
document.setTableTextLeft(table_base,0,8,"项目号:");
document.setTableTextLeft(table_base,1,8,"文件号:");
document.setTableTextLeft(table_base,2,8,"CADD号:");
document.setTableTextLeft(table_base,3,8,"设计阶段:施工图");
document.setTableTextLeft(table_base,4,8,"日期:2020.");
document.setTableTextCenter(table_base,5,8,"第 1 页 共 3 页");
document.setTableTextCenter(table_base,5,11,"0 版");
//填充实体表头
document.setTableTextCenter(table_base,6,0,"序号");
document.setTableTextCenter(table_base,6,1,"名称、规格及标准号");
document.setTableTextCenter(table_base,6,6,"单位");
document.setTableTextCenter(table_base,6,7,"数量");
document.setTableTextCenter(table_base,6,8,"净量");
document.setTableTextCenter(table_base,6,9,"总量");
document.setTableTextCenter(table_base,6,10,"备注");
//遍历实体数据填充
for (int i = 0; i < listQuery.size(); i++) {
//设置实体数据信息
document.mergeCellsHorizontal(table_base,7+i,1,5);
document.mergeCellsHorizontal(table_base,7+i,10,11);
MaterialEntity entity = listQuery.get(i);
Integer sort = entity.getSort();
document.setTableTextCenter(table_base,7+i,0,StringUtils.isEmpty(sort)?"":sort.toString());
String specifications = entity.getSpecifications();
document.setTableTextCenter(table_base,7+i,1,StringUtils.isEmpty(specifications)?"":specifications);
String unit = entity.getUnit();
document.setTableTextCenter(table_base,7+i,6,StringUtils.isEmpty(unit)?"":unit);
String num = entity.getNum();
document.setTableTextCenter(table_base,7+i,7,StringUtils.isEmpty(num)?"":num);
Double netWeight = entity.getNetWeight();
document.setTableTextCenter(table_base,7+i,8,StringUtils.isEmpty(netWeight)?"":netWeight.toString());
Double total = entity.getTotal();
document.setTableTextCenter(table_base,7+i,9,StringUtils.isEmpty(total)?"":total.toString());
String remark = entity.getRemark();
document.setTableTextCenter(table_base,7+i,10,StringUtils.isEmpty(remark)?"":remark);
}
//end数据填充部分结束
document.setColWidth(table_base);
//200
// document.createTable()
document.write(out);
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
前端代码部分
测试页面
写了一个测试页面,供调用接口,进行测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>首页</title>
<link rel="stylesheet" type="text/css" href="/thirdparty/jquery-easyui-1.5.5.4/themes/default/easyui.css">
<link rel="stylesheet" type="text/css" href="/thirdparty/jquery-easyui-1.5.5.4/themes/icon.css">
<link rel="stylesheet" type="text/css" href="/thirdparty/jquery-easyui-1.5.5.4/themes/color.css">
<script type="text/javascript" src="/thirdparty/jquery-easyui-1.5.5.4/jquery.min.js"></script>
<script type="text/javascript" src="/thirdparty/jquery-easyui-1.5.5.4/jquery.easyui.min.js"></script>
<script type="text/javascript" src="/thirdparty/jquery-easyui-1.5.5.4/locale/easyui-lang-zh_CN.js"></script>
<script type="text/javascript" src="/thirdparty/jquery-easyui-1.5.5.4/easyui-msg.js"></script>
<script type="text/javascript" src="thirdparty/jquery-easyui-1.5.5.4/md5.js" type="text/javascript"></script>
</head>
<body>
<form id="download_word" method="post" action="/word/exportDesignWord">
<input name="name" id="download_word_name">
<button onclick="exportMaterial">下载</button>
</form>
<script>
function exportMaterial() {
$("#download_word").submit();
}
</script>
</body>
</html>
项目中导出了两张不同类型表格,导出第二张及以上表格时,需要类似于第一张表格的操作,在第一张下面,继续创建表格,然后遍历填充即可,deomo中仅提供了导出一张表格的方法,后续将对代码进行整理后,更新导出多套表格的代码;
以上部分不为项目中正式使用的代码,仅为demo测试,但基本用法已全部展示,根据自己项目的实际需要,可进行相应的调节。
如果觉得笔者写的不错,欢迎点赞,转载,转载时请注明出处,谢谢。