doc模版导出

一 快速入门

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

推荐使用工厂 TablesRowsCells 构建表格模型。

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()
    });
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
要根据Word模板导出Java代码,可以使用Apache POI和FreeMarker这两个开源库来实现。具体步骤如下: 1. 使用Apache POI读取Word模板文件,获取模板中的内容。 2. 将读取到的内容传递给FreeMarker进行解析,生成需要填充的数据。 3. 将生成的数据填充到Word模板中,生成最终的Word文档。 以下是实现的简单示例代码: ``` // 读取Word模板文件 FileInputStream fis = new FileInputStream("template.docx"); XWPFDocument doc = new XWPFDocument(fis); // 获取模板中的内容 List<XWPFParagraph> paragraphs = doc.getParagraphs(); List<XWPFTable> tables = doc.getTables(); // 使用FreeMarker生成数据 Configuration cfg = new Configuration(Configuration.VERSION_2_3_0); cfg.setDefaultEncoding("UTF-8"); cfg.setClassForTemplateLoading(this.getClass(), "/templates"); Map<String, Object> data = new HashMap<>(); data.put("name", "张三"); data.put("age", 20); Template template = cfg.getTemplate("template.ftl"); StringWriter writer = new StringWriter(); template.process(data, writer); // 将生成的数据填充到Word模板中 String content = writer.toString(); for (XWPFParagraph paragraph : paragraphs) { String text = paragraph.getText(); if (text.contains("${")) { text = text.replaceAll("\\$\\{.*?\\}", content); paragraph.setText(text); } } for (XWPFTable table : tables) { List<XWPFTableRow> rows = table.getRows(); for (XWPFTableRow row : rows) { List<XWPFTableCell> cells = row.getTableCells(); for (XWPFTableCell cell : cells) { String text = cell.getText(); if (text.contains("${")) { text = text.replaceAll("\\$\\{.*?\\}", content); cell.setText(text); } } } } // 保存最终的Word文档 FileOutputStream fos = new FileOutputStream("output.docx"); doc.write(fos); fos.close(); ``` 以上代码仅供参考,具体实现还需要根据实际需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旧城以西没有故事

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值