一 快速入门
1. 依赖
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.0</version>
</dependency>
2. 模板文件
template.docx
{{wo}}--{{xi}}--{{huan}}--{{ni}}
3. 代码示例
XWPFTemplate template = XWPFTemplate.compile("/Users/zhangcheng/Desktop/template.docx").render(
new HashMap<String, Object>() {{
put("wo", "我");
put("xi", "喜");
put("huan", "欢");
put("ni", "你");
}});
template.writeAndClose(Files.newOutputStream(Paths.get("/Users/zhangcheng/Desktop/output.docx")));
// template.writeAndClose(new FileOutputStream("output.docx")); //输出到当前文件夹下
compile
编译模板
render
渲染数据
write
输出到流
TDO模式:Template + data-model = output
3.1 Template:模板
所有的标签都是以{{
开头,以}}
结尾,标签可以出现在任何位置,包括页眉,页脚,表格内部,文本框等,表格布局可以设计出很多优秀专业的文档,推荐使用表格布局。
3.2 data-model:数据
数据类似于哈希或者字典,可以是Map结构(key是标签名称)
Map<String, Object> data = new HashMap<>();
data.put("name", "Sayi");
data.put("start_time", "2019-08-04");
可以是对象(属性名是标签名称):
@Data
class Student {
public String name;
public Integer age;
public String sex;
public Teacher teacher;
}
@Data
class Teacher {
private String name;
private Integer age;
private String sex;
}
数据可以是树结构,每级之间用点来分隔开,比如{{teacher.name}}标签对应的数据是teacher对象的name属性值。
模板:
学生---{{name}}---{{age}}---{{sex}}
老师---{{teacher.name}}---{{teacher.age}}---{{teacher.sex}}
代码示例
Student student = new Student();
student.setName("小张");
student.setAge(23);
student.setSex("男");
Teacher teacher = new Teacher();
teacher.setName("小黄");
teacher.setAge(21);
teacher.setSex("女");
student.setTeacher(teacher);
XWPFTemplate render = XWPFTemplate.compile("/Users/zhangcheng/Desktop/template.docx").render(student);
render.writeAndClose(Files.newOutputStream(Paths.get("/Users/zhangcheng/Desktop/output.docx")));
二 标签
1. 文本
{{var}}
数据模型
String
:文本TextRenderData
:有样式的文本HyperlinkTextRenderData
:超链接和锚点文本Object
:调用 toString() 方法转化为文本
XWPFTemplate template = XWPFTemplate.compile("/Users/zhangcheng/Desktop/template.docx").render(
new HashMap<String, Object>() {{
put("wo", "我");
put("xi", new TextRenderData("00FFFF","喜"));
put("huan", new HyperlinkTextRenderData("欢","https://www.baidu.com/"));
put("ni", new HyperlinkTextRenderData("你","student:name")); //这个没搞懂
}});
template.writeAndClose(Files.newOutputStream(Paths.get("/Users/zhangcheng/Desktop/output.docx")));
除了new操作符,还提供了更加优雅的工厂 Texts
和链式调用的方式轻松构建文本模型。
XWPFTemplate template = XWPFTemplate.compile("/Users/zhangcheng/Desktop/template.docx").render(
new HashMap<String, Object>() {{
put("wo", Texts.of("我").color("00FFFF").create());
put("xi",Texts.of("喜").link("https://www.baidu.com/").create());
put("huan", Texts.of("欢").anchor("teacher:name")); //没搞懂
}});
template.writeAndClose(Files.newOutputStream(Paths.get("/Users/zhangcheng/Desktop/output.docx")));
所见即所得,标签的样式会应用到替换后的文本上,也可以通过代码设定文本的样式。
TextRenderData的结构体
{
"text": "Sayi",
"style": {
"strike": false, //删除线
"bold": true, //粗体
"italic": false, //斜体
"color": "00FF00", //颜色
"underLine": false, //下划线
"fontFamily": "微软雅黑", //字体
"fontSize": 12, //字号
"highlightColor": "green", //背景高亮色
"vertAlign": "superscript", //上标或者下标
"characterSpacing" : 20 //间距
}
}
文本换行使用 \n
字符。
2. 图片
图片标签以@开始:{{@var}}
数据模型:
String
:图片url或者本地路径,默认使用图片自身尺寸PictureRenderData
ByteArrayPictureRenderData
FilePictureRenderData
UrlPictureRenderData
推荐使用工厂 Pictures
构建图片模型。
XWPFTemplate template = XWPFTemplate.compile("/Users/zhangcheng/Desktop/template.docx").render(
new HashMap<String, Object>() {{
// 指定图片路径
put("image", "/Users/zhangcheng/Pictures/222.png");
// svg图片
put("svg", "https://img.shields.io/badge/jdk-1.6%2B-orange.svg");
// 设置图片宽高
put("image1", Pictures.ofLocal("/Users/zhangcheng/Pictures/222.png").size(100, 100).create());
// 图片流
put("streamImg", Pictures.ofStream(Files.newInputStream(Paths.get("/Users/zhangcheng/Pictures/222.png")), PictureType.PNG).size(200, 200).create());
// 网络图片(注意网络耗时对系统可能的性能影响)
put("urlImg", Pictures.ofUrl("http://deepoove.com/images/icecream.png").size(100, 100).create());
// java图片
put("buffered", Pictures.ofBufferedImage(bufferImage, PictureType.PNG).size(100, 100).create());
}});
template.writeAndClose(Files.newOutputStream(Paths.get("/Users/zhangcheng/Desktop/output.docx")));
图片支持BufferedImage,这意味着我们可以利用Java生成图表插入到word文档中
FilePictureRenderData的结构体
{
"pictureType" : "PNG", //图片类型
"path": "logo.png", //图片路径
"pictureStyle": {
"width": 100, //宽度,单位是像素
"height": 100 //高度,单位是像素
},
"altMeta": "图片不存在" //当无法获取图片时展示的文字
}
3. 表格
表格标签以#开始:{{#var}}
数据模型:
TableRenderData
推荐使用工厂 Tables
、 Rows
和 Cells
构建表格模型。
XWPFTemplate template = XWPFTemplate.compile("/Users/zhangcheng/Desktop/template.docx").render(
new HashMap<String, Object>() {{
// Example 1. 基础表格示例 一个3行3列的表格
put("table1", Tables.of(new String[][]{
new String[]{"11", "12", "13"},
new String[]{"21", "22", "23"},
new String[]{"31", "32", "33"},
}).border(BorderStyle.DEFAULT).create());
// Example 2. 表格样式示例 第0行居中且背景为蓝色的表格
RowRenderData row0 = Rows.of("姓名", "学历", "家乡").textColor("FFFFFF")
.bgColor("4472C4").center().create();//设置样式用Rows.of
RowRenderData row1 = Rows.create("李四", "博士", "南京");
RowRenderData row2 = Rows.create("张三", "硕士", "扬州");//不设置样式,直接Rows.create
put("table2", Tables.create(row0, row1, row2));
// Example 3. 表格合并示例 合并第1行所有单元格的表格
RowRenderData row00 = Rows.of("列0", "列1", "列2","列3").center().bgColor("4472C4").create();
RowRenderData row01 = Rows.create("没有数据", null, null,null);
RowRenderData row02 = Rows.create("20", "21", null,"22");
MergeCellRule rule = MergeCellRule.builder() .map(MergeCellRule.Grid.of(1, 0), MergeCellRule.Grid.of(1, 2))//坐标,从哪里到哪里
.map(MergeCellRule.Grid.of(2,2),MergeCellRule.Grid.of(2,3))//坐标从(0,0)开始
.build();
put("table3", Tables.of(row00, row01,row02).mergeRule(rule).create());
}});
template.writeAndClose(Files.newOutputStream(Paths.get("/Users/zhangcheng/Desktop/output.docx")));
TableRenderData表格模型在单元格内可以展示文本和图片,同时也可以指定表格样式、行样式和单元格样式,而且在N行N列渲染完成后可以应用单元格合并规则 MergeCellRule ,从而实现更复杂的表格。
{
"rows": [ //行数据
{
"cells": [ //单元格数据
{
"paragraphs": [ //单元格内段落
{
"contents": [
{
[TextRenderData] //单元格内文本
},
{
[PictureRenderData] //单元格内图片
}
],
"paragraphStyle": null //单元格内段落文本的样式:对齐
}
],
"cellStyle": { //单元格样式:垂直对齐方式,背景色
"backgroundColor": "00000",
"vertAlign": "CENTER"
}
}
],
"rowStyle": { //行样式:行高(单位cm)
"height": 2.0f
}
}
],
"tableStyle": { //表格样式:表格对齐、边框样式
"width": 14.63f, //表格宽度(单位cm),表格的最大宽度 = 页面宽度 - 页边距宽度 * 2,页面宽度为A4(20.99 * 29.6,页边距为3.18 * 2.54)的文档最大表格宽度14.63cm。
"colWidths": null
},
"mergeRule": { //单元格合并规则,比如第0行第0列至第1行第2列单元格合并
"mapping": {
"0-0": "1-2"
}
}
}
产品需求中表格的布局和样式可能很复杂,可以尝试一些已有表格插件来解决,参见更多插件列表。
我们也可以编写插件,完全由自己生成整个表格,前提是需要熟悉Apache POI XWPFTable相关API,但是自由度最高:参见 开发一个插件。
4. 列表
列表标签以开始:{{var}}
数据模型:
List<String>
NumberingRenderData
推荐使用工厂 Numberings
构建列表模型。
XWPFTemplate template = XWPFTemplate.compile("/Users/zhangcheng/Desktop/template.docx").render(
new HashMap<String, Object>() {{
put("list", Numberings.create("Plug-in grammar",
"Supports word text, pictures, table...",
"Not just templates"));
}});
template.writeAndClose(Files.newOutputStream(Paths.get("/Users/zhangcheng/Desktop/output.docx")));
编号样式支持罗马字符、有序无序等,可以通过 Numberings.of(NumberingFormat)
来指定
DECIMAL //1. 2. 3.
DECIMAL_PARENTHESES //1) 2) 3)
BULLET //● ● ●
LOWER_LETTER //a. b. c.
LOWER_ROMAN //i ⅱ ⅲ
UPPER_LETTER //A. B. C.
NumberingRenderData可以创建多级列表,但是推荐使用区块对:区块对的循环功能可以很好的循环列表,并且保持有序列表编号有序。
5. 区块对
区块对由前后两个标签组成,开始标签以?标识,结束标签以/标识:{{?sections}}{{/sections}}
区块对开始和结束标签中间可以包含多个图片、表格、段落、列表、图表等,开始和结束标签可以跨多个段落,也可以在同一个段落,但是如果在表格中使用区块对,开始和结束标签必须在同一个单元格内,因为跨多个单元格的渲染行为是未知的。
区块对在处理一系列文档元素的时候非常有用,位于区块对中的文档元素可以被渲染零次,一次或N次,这取决于区块对的取值。
-
False或空集合
隐藏区块中的所有文档元素
-
非False且不是集合
显示区块中的文档元素,渲染一次
-
非空集合
根据集合的大小,循环渲染区块中的文档元素
集合是根据值的类型是否实现了 Iterable
接口来判断。
三 前后端分离案例
后端
//准备实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
class Student {
public Long num;
public String name;
public String sex;
public Double count;
}
//集合
List<Student> studentList = new ArrayList<>(
Arrays.asList(
new Student(1L, "小张", "男", 99.9),
new Student(22L, "小黄", "女", 99.8),
new Student(333L, "小程", "男", 66.6)
)
);
//准备数据
Map<String, Object> map = new HashMap<String, Object>() {{
if ("1".equals(type)){
put("student", studentList);
}else {
put("teacherList", teacherList);
}
}};
List<String> nameList = new ArrayList<>();
nameList.add("student");
//调用方法
poiTlWord(map, "/Users/zhangcheng/Desktop/auctionResult.docx", nameList, "xxxx");
/**
* 根据word模版生成word文档并通过流的方式下载
*
* @param map 数据集合
* @param originPath 模版文件路径
* @param nameList 循环表格名称
* @param fileName 生成的文件名称
*/
public void poiTlWord(Map<String, Object> map, String originPath, List<String> nameList, String fileName) {
OutputStream out = null;
BufferedOutputStream bos = null;
XWPFTemplate template;
String filePoiName;
try {
if (CollectionUtils.isNotEmpty(nameList)) {
//如果有循环添加的数据表格的话 则绑定
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
ConfigureBuilder builder = Configure.builder();
nameList.forEach(l -> builder.bind(l, policy));
Configure config = builder.build();
template = XWPFTemplate.compile(originPath, config).render(map);
} else {
template = XWPFTemplate.compile(originPath).render(map);
}
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletResponse response = ((ServletRequestAttributes) requestAttributes).getResponse();
//设置返回类型及文件名
response.setContentType("application/octet-stream;charset=UTF-8");
response.setCharacterEncoding("utf-8");
filePoiName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename=\"" + filePoiName + ".docx" + "\"");
//获取返回输出流
out = response.getOutputStream();
bos = new BufferedOutputStream(out);
template.write(bos);
bos.flush();
out.flush();
PoitlIOUtils.closeQuietlyMulti(template, bos, out);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (out != null) out.close();
if (bos != null) bos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
前端
fetch(API.getHost()+'/auction/api/website/exportTemplate/sampleLetter', {
method: 'POST',
body: JSON.stringify({ //参数json
goodsId:this.goodsId
}),
headers: {
'Content-Type': 'application/json',
'auth-user-id': JSON.parse(this.personalInfo).id //如果需要token就传token
},
mode:'cors'
})
.then(res => res.blob())//创建一个blob对象
.then(data => {
let blobUrl = window.URL.createObjectURL(data);
const a = document.createElement('a');//创建一个<a></a>标签
a.download = '废旧物资网上拍卖看样通知函.docx';//文件名称
a.href = blobUrl;// response is a blob
a.style.display = "none"; // 障眼法藏起来a标签
document.body.appendChild(a); // 将a标签追加到文档对象中
a.click();
a.remove()
});