Skip to content

3 模板导出

guanquan.wang edited this page Nov 21, 2024 · 11 revisions

从v0.5.14开始EEC新增模板工作表TemplateSheet,它支持指定一个已有的Excel文件作为模板导出,TemplateSheet将复制模板工作表的样式并替换占位符, 同时TemplateSheet也可以和其它Worksheet混用,这意味着可以添加多个模板工作表和普通工作表。 创建模板工作表需要指定模板文件,它可以是本地文件也可是输入流InputStream,支持的类型包含xls 和xlsx两种格式,除模板文件外还需要指定工作表, 未指定工作表时默认以第一个工作表做为模板。

绑定值

TemplateSheet工作表导出时不受ExcelColumn注解限制,导出的数据范围由模板内占位符决定,默认占位符由一对关闭的大括号${key}组成, 虽然占位符与EL表达式写法相同但模板占位符并不具备EL的能力,所以无法使用${1 + 2}${System.getProperty("user.name")}这类语句来做运算, 模板占位符仅做替换不做运算所以不需要担心安全漏洞问题。

setData(java.lang.Object)方法为占位符绑定值,支持对象、Map、Array和List,数据量较大时可绑定一个数据生产者data-supplier来分片拉取数据, 它被定义为BiFunction<Integer, T, List<T>>,其中第一个入参Integer表示已拉取数据的记录数(并非已写入数据), 第二个入参T表示上一批数据中最后一个对象,业务端可以通过这两个参数来计算下一批数据应该从哪个节点开始拉取, 通常你可以使用第一个参数除以每批拉取的数据大小来确定当前页码,如果数据已排序则可以使用T对象的排序字段来计算下一批数据的游标以跳过 limit ... offset ... 分页查询从而大大提升取数性能。

new Workbook("模板测试")
    // 模板工作表
   .addSheet(new TemplateSheet(Paths.get("F:/excel/template.xlsx"))
       // 绑定用户列表
       .setData(userList)
        // 设置一个数据生产者 data-supplier分片查询数据
       .setData((i, lastOne) -> queryUser(i > 0 ? ((User)lastOne).getId() : 0))
   // 普通对象数组工作表
   .addSheet(new ListSheet<>().setData(list)))
   .writeTo(Paths.get("F:/excel"));

命名空间

每个占位符都有一个命名空间,格式为${namespace.key}它用于区分不同的数据域,例如汇总数据和列表数据或多个列表数据, 当前只支持一级命名空间如果对象套对象则需要在外部拆分并以不同的命名空间设值

有如下模板,它有3个命名空间,分别为”默认“,”list“和”summary“

命名空间模板

测试代码

// 准备测试数据 >>>
Map<String, Object> main = new HashMap<>();
main.put("gysName", " 供应商A");
main.put("gsName", "ABC公司");
main.put("jsName", "亚瑟");
main.put("cgDate", new Date());
main.put("orderNo", "0001");
main.put("orderStatus", "OK");

List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> row1 = new HashMap<>();
row1.put("xh", 1);
row1.put("jpCode", "45A3495C72");
row1.put("jpName", "name1");
row1.put("num", 10);
row1.put("price", 10);
row1.put("amount", 100);
row1.put("tax", 0.6);
row1.put("taxPrice", 11.6);
row1.put("taxAmount", 116);
row1.put("remark", "备注1");
list.add(row1);
Map<String, Object> row2 = new HashMap<>();
row2.put("xh", 2);
row2.put("jpCode", "F2454E321436");
row2.put("jpName", "name2");
row2.put("num", 20);
row2.put("price", 20);
row2.put("amount", 200);
row2.put("tax", 0.6);
row2.put("taxPrice", 21.2);
row2.put("taxAmount", 212);
row2.put("remark", "备注2");
list.add(row2);

Map<String, Object> summary = new HashMap<>();
summary.put("nums", 30);
summary.put("priceTotal", 30);
summary.put("amountTotal", 300);
summary.put("taxTotal", 0.6);
summary.put("taxPriceTotal", 32.8);
summary.put("taxAmountTotal", 328);
// <<< 测试数据准备结束

new Workbook()
    .addSheet(new TemplateSheet(testResourceRoot().resolve("template2.xlsx"))
        .setData(main)  <- 绑定命名空间null
        .setData("list", list) <- 绑定命名空间list
        .setData("summary", summary) <- 绑定命名空间summary
    ).writeTo(Paths.get("F:/excel/命名空间测试.xlsx"));

效果如下:

内置函数导出结果

数据格式化

只需要在模板文件中设置好格式化即可,单元格设置的任意格式化都将被复制,如上示例中价格单元格包含格式化¥0.00_),模板文件看不出效果但结果文件可以看到效果(结果为数字所以有格式化效果)

内置函数

占位符中包含三个内置函数它们分别为[@link:][@list:][@media:],分别用于设置单元格的值为超链接、序列和图片, 其中序列的值可以从源工作表中获取也可以使用setData方法来设置,内置函数必须独占一个单元格且仅识别固定的三个内置函数, 任意其它命令将被识别为普通命令空间

占位符整体样式:[@内置函数:][命名空间][.]<占位符>

有如下模板

内置函数模板

// 准备测试数据 >>>
List<Map<String, Object>> list = new ArrayList<>();
row1.put("name", "张三");
row1.put("age", 6);
row1.put("sex", "男");
row1.put("pic", "https://zhangsan.png");
row1.put("jumpUrl", "https://jianli.com/zhangsan");
list.add(row1);

Map<String, Object> row2 = new HashMap<>();
row2.put("name", "李四");
row2.put("age", 8);
row2.put("sex", "女");
row2.put("pic", "https://lisi.png");
row2.put("jumpUrl", "https://jianli.com/lisi");
list.add(row2);
// <<< 测试数据准备结束

new Workbook()
   // 模板工作表
   .addSheet(new TemplateSheet(Paths.get("F:/excel/template.xlsx"))
       // 替换模板中占位符
       .setData(list)
       // 替换模板中"@list:sex"值为性别序列
       .setData("@list:sex", Arrays.asList("未知", "男", "女")))
   .writeTo(Paths.get("F:/excel/内置函数测试.xlsx"));

效果如下:

内置函数导出结果

性别列为"序列",头像列为"图片",简历原件为"超链接"

多种混合工作表

在EEC中Worksheet及其子类均被视为数据源,这些数据源均可以混合使用,输出协议由WorksheetWriter决定,目前仅支持xlsx和csv格式,所以即使模板为xls格式输出也将是xlsx格式

new Workbook()
    .addSheet(new TemplateSheet(Paths.get("F:/excel/1.xlsx"))) // <- xlsx模板工作表
    .addSheet(new TemplateSheet(Paths.get("F:/excel/2.xls"))) // <- xls模板工作表
    .addSheet(new ListSheet<>(randomTestData())) // <- 普通工作表
    .writeTo(Paths.get("F:/excel/混合模板.xlsx"));

兼容性

为了解决切换到EEC后导致现有模板失效从而大面积修改模板的问题,EEC提供了setPrefixsetSuffix两个方法来修改占位符前缀和后缀,如现有模板占位符为{key} 则可以使用setPrefix("{")来重置前缀,这样你不需要修改现有模板来完成适配。

new Workbook()
   // 模板工作表
   .addSheet(new TemplateSheet(Paths.get("F:/excel/template.xlsx"))
       .setPrefix("{") // <- 重置前缀为"{"
       .setData(list))
   .writeTo(Paths.get("F:/excel/内置函数测试.xlsx"));

特殊说明

TemplateSheet工作表使用ExcelReader读取源文件,并复制样式等信息到新的工作表,它并不是直接在原工作表中追加数据,所以会丢失一些信息(只能读取当前ExcelReader所支持的内容)。

除此之外v0.5.14版本还有一些功能限制,具体表现如下

  • 不支持多Table,如果Table列与其余固定文本一起时固定文本也将被复制
  • 替换占位符时不解析现有对象中的ExcelColumn注解,所以在实体里设置的注解属性完全无效
  • 模板为xls时颜色会出现偏差,我尝试用POI读取颜色时也出现同样问题
  • 暂时不支持自动分页,模板有更复杂的内容,无法处理分页后哪些部分需要复制到新的工作表,所以暂时不支持分页
  • 模板中包含双色填充时导出结果只会保留color1的颜色
  • 由于eec读取xls图片有BUG,所以模板文件中有图片时可能导致导出异常