模板导出excel(天宫版)
# 功能介绍
tiangong-core 2.2.4 版本以上
框架内置的独立的excel导出组件, 根据模板导出excel或pdf
功能跟老框架类似, 提供了更多的扩展,无需操作原生的workboot
- 自定义单元格样式
- 自定义单元格内容
- 图片任意位置摆放
- 完全由数据驱动渲染
- 少量的代码量
适合咱们公司大部分的复杂场景, 比如报告单表头+表头
后续长期维护和迭代的组件
# 模板介绍
# 模板放在哪
框架默认是从resources中寻找, 当然了, 你可以在resources中自己再建一个单独的文件夹, 存放你的模板
- 存放在resources下面
- 也可以存放在jar包所在目录的config文件夹下, 找不到会去resources里面找, (外部config文件夹优先级更高), 不修改数据只调整样式就无需重新打包项目
# 生成的结果在哪
结果默认在项目根目录下, tg_excel文件下, yyyyMMdd日期文件夹下
如果是部署在服务器, 就是在jar包同级的目录下, 和logs文件夹在一起
# 模板语法
语法 | 说明 |
---|---|
${iEFlag} | 从数据中提取iEFlag的变量的值替换 |
#{classScoreList[i].name} | #表示循环, classScoreList后面是[i], 则表示它是集合数据, 组建会循环这行 |
#{classScoreList[0].name} | 取classScoreList集合中的第一条数据的name值, [0]就表示是固定的值 |
*{logo} | *表示这个位置是图片, 后台数据中有个logo的变量, 值就是图片本地的path地址 |
具体的看以下demo
# 列表循环
// 准备数据
Map<String,Object> data = new HashMap<>();
List<ClassScore> classScoreList = Arrays.asList(
new ClassScore(1,"高一1班", "张三", BigDecimal.valueOf(90), BigDecimal.valueOf(95), BigDecimal.valueOf(100)),
new ClassScore(1,"高一1班", "李四", BigDecimal.valueOf(80), BigDecimal.valueOf(100), BigDecimal.valueOf(90)),
new ClassScore(1,"高一1班", "王五", BigDecimal.valueOf(80), BigDecimal.valueOf(95), BigDecimal.valueOf(90)),
new ClassScore(2,"高一2班", "赵六", BigDecimal.valueOf(95), BigDecimal.valueOf(70), BigDecimal.valueOf(95)),
new ClassScore(2,"高一2班", "孙七", BigDecimal.valueOf(60), BigDecimal.valueOf(60), BigDecimal.valueOf(80)),
new ClassScore(2,"高一2班", "周八", BigDecimal.valueOf(55), BigDecimal.valueOf(100), BigDecimal.valueOf(100)),
new ClassScore(2,"高一3班", "吴九", BigDecimal.valueOf(81), BigDecimal.valueOf(91), BigDecimal.valueOf(81)),
new ClassScore(2,"高一3班", "郑十", BigDecimal.valueOf(82), BigDecimal.valueOf(92), BigDecimal.valueOf(82)));
data.put("classScoreList",classScoreList);
// 1.创建模板
TgExcelTemplate excelTemplate = new TgExcelTemplate("score.xlsx", data);
// 2.导出数据, path就是文件路径 D:\eciProjects\tiangong_back\tg_excel\20240716\tgExcelTemplate20240716121014.slxs
String path = excelTemplate.export(0);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
说明:
需要循环一定要用#开头, [i]占位
创建模板: new TgExcelTemplate("score.xlsx", data);
score.xlsx在resource/templates 下面, 如果还有子文件夹, 可以这样写: "/excel/score.xlsx"
data: 就是数据, 目前支持一个对象, 或一个map, 不可以直接放一个集合
export可以传入sheet下标, 默认从0开始, 结果就是文件本地路径
# 表格+列表
// 创建模板, new SchoolData()模拟对象数据
TgExcelTemplate excelTemplate = new TgExcelTemplate("school.xlsx", new SchoolData());
// 需要打印pdf
excelTemplate.setToPdf(true);
// 因为需要合并单元格, 获取第一个sheet模板
TgSheetTemplate sheetTemplate = new TgSheetTemplate(0);
// sheet1 设置需要合并的列, 从 A13开始, 向下分组合并
sheetTemplate.addMergedColumn(new TgGroupMerged("A",13));
// 将sheet模板添加到excel模板
excelTemplate.addSheetTemplate(sheetTemplate);
// 导出数据, 因为上面已经添加了, 可以不用指定sheet index, path就是文件路径
String path = excelTemplate.export();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 模拟的school数据
@Data
public class SchoolData {
// 标题
private String title;
// 前3名
private GradesRank gradesRank;
// 评论
private List<String> commentsList;
// 班级分数
private List<ClassScore> classScoreList;
// 校长评论
private String principalComment;
// logo
private String logo;
public SchoolData(){
this.title = "中学成绩单";
GradesRank gradesRank = new GradesRank();
Score top1 = new Score("张三", BigDecimal.valueOf(90), BigDecimal.valueOf(95), BigDecimal.valueOf(100));
Score top2 = new Score("李四", BigDecimal.valueOf(80), BigDecimal.valueOf(100), BigDecimal.valueOf(90));
Score top3 = new Score("赵六",BigDecimal.valueOf(95),BigDecimal.valueOf(70),BigDecimal.valueOf(95));
gradesRank.setTop1(top1);
gradesRank.setTop2(top2);
gradesRank.setTop3(top3);
this.gradesRank = gradesRank;
List<String> commentsList = Arrays.asList(
"我们都应该向成绩好的同学们学习!加油!",
"学好数理化,走遍天下都不怕",
"数学不学好,一切皆为0");
this.commentsList = commentsList;
List<ClassScore> classScoreList = Arrays.asList(
new ClassScore(1,"高一1班", "张三", BigDecimal.valueOf(90), BigDecimal.valueOf(95), BigDecimal.valueOf(100)),
new ClassScore(1,"高一1班", "李四", BigDecimal.valueOf(80), BigDecimal.valueOf(100), BigDecimal.valueOf(90)),
new ClassScore(1,"高一1班", "王五", BigDecimal.valueOf(80), BigDecimal.valueOf(95), BigDecimal.valueOf(90)),
new ClassScore(2,"高一2班", "赵六", BigDecimal.valueOf(95), BigDecimal.valueOf(70), BigDecimal.valueOf(95)),
new ClassScore(2,"高一2班", "孙七", BigDecimal.valueOf(60), BigDecimal.valueOf(60), BigDecimal.valueOf(80)),
new ClassScore(2,"高一2班", "周八", BigDecimal.valueOf(55), BigDecimal.valueOf(100), BigDecimal.valueOf(100)),
new ClassScore(2,"高一3班", "吴九", BigDecimal.valueOf(81), BigDecimal.valueOf(91), BigDecimal.valueOf(81)),
new ClassScore(2,"高一3班", "郑十", BigDecimal.valueOf(82), BigDecimal.valueOf(92), BigDecimal.valueOf(82)));
this.classScoreList = classScoreList;
this.principalComment = "你们都是祖国未来的希望!";
this.logo = "C:/Users/doutingjie/Desktop/kibana.png";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
" ${} " 数据采用直接获取并占位
" #{} " 循环第13行, 最后的校长评语会一直下沉
" *{} " 放了一个图片logo, 我在数据里面也指定了图片地址: this.logo = "C:/Users/doutingjie/Desktop/kibana.png";
对sheet表格指定了相同数据需要进行合并: sheetTemplate.addMergedColumn(new TgGroupMerged("A",13)); 指定从A13往下合并
合并的前提是, 数据就是排好序的, 组件只是负责遇到相同的值就合并, 仅此而已, 一切看数据
# 组件提供的方法
**如果现有的模板无法满足需求, 可以看看以下由框架封装后的api, 可以手动调整模板, 给模板赋值 **
- 设置导出文件的名称
TgExcelTemplate excelTemplate = new TgExcelTemplate("dec.xlsx", getDec());
// 不给就是默认的 tgExcelTemplate20240716121014.slxs
excelTemplate.setExportName("文件名字");
2
3
- 拷贝sheet / 删除sheet
excelTemplate.copyTgSheet(0);
excelTemplate.removeSheet(1);
2
- 获取sheet表格对象
TgSheetTable sheetTable = excelTemplate.getSheetTable(0);
// rowMap里面存储了所有单元格的信息
Map<Integer, TgRow> rowMap = sheetTable.getRowMap();
rowMap.forEach((rowNum, tgCells) -> {
// 遍历row, 这里的行号都是真实的, 从1开始的, 和excel一一对应的
System.out.println("------ rowNum:" + rowNum);
// 遍历cell
for (TgCell tgCell : tgCells) {
System.out.println("cellValue:"+tgCell.getValue());
// 可以修改或设置值给模板
tgCell.setValue("${xxxx}");
// 可以手动设置一些样式, 比如边线, 字体粗细等等...
tgCell.setCellStyle(null);
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 复制行
Map<Integer, TgRow> rowMap = sheetTable.getRowMap();
// 获取13行
TgRow row = rowMap.get(13);
// 复制13行, 并追加在14行, 原来14行的东西会依次向下移动
sheetTable.copyRowAfter(row);
2
3
4
5
- 删除行
sheetTable.removeRow(10);
# 报关单打印案例
这个模板有很多特殊性:
- 表头 表头分页了, 两页完全不一样
- 表体最多50条, 所以理论上可以建好4个sheet页表体放那, 回头不用的可以删掉多余的, 我采取的也是这样的策略
- 表头固定放6行, 不足的, 需要给空白行填充, 所以我数据也是直接写死了6行, 方便调整页面大小
- 表体固定的14行, 不足的, 需要给空白行填充, 所以我数据也是直接写死了14行, 方便调整页面大小, 后面可以采用复制sheet2达到更多表体的渲染
- 海关图表是固定在左上角的, 可以优先放好, 条形码*{tiao} 二维码*{er} , 可以先占位, 后面由组件填充
- 页码/页数: 需要由后台计算, 留在最后从代码填充上去
// 创建模板
TgExcelTemplate excelTemplate = new TgExcelTemplate("dec.xlsx", getDec());
excelTemplate.setToPdf(true);
List<Body> body = (List<Body>) getDec().get("body");
//----------------------------------------以下属于业务单独处理--------------------------------------------------
String export = "";
if (body.size() <= 6) {
TgSheetTemplate sheetTemplate = new TgSheetTemplate(0);
excelTemplate.addSheetTemplate(sheetTemplate);
excelTemplate.getSheetTable(0).getCell(4, "AM").setValue("1/1");
excelTemplate.removeSheet(1);
excelTemplate.removeSheet(2);
export = excelTemplate.export(0);
} else {
// 第二页开始, 不足14条, 自己补齐空白行
// 需要几个表体sheet? 自己业务自己计算 20 - 6 / 14 = 1
int needBodySheetCount = (body.size() - 6) / 14; // 1
TgSheetTemplate sheetTemplate1 = new TgSheetTemplate(0);
TgSheetTemplate sheetTemplate2 = new TgSheetTemplate(1);
excelTemplate.addSheetTemplate(sheetTemplate1);
excelTemplate.addSheetTemplate(sheetTemplate2);
// 设置页码
excelTemplate.getSheetTable(0).getCell(4, "AM").setValue("1/2");
excelTemplate.getSheetTable(1).getCell(4, "AM").setValue("2/2");
// 设置21行为实线
TgSheetTable sheetTable = excelTemplate.getSheetTable(0);
sheetTable.getRowMap().forEach((index, row) -> {
if (index == 21) {
row.getColCellMap().forEach((cellName, cell) -> {
if (cellName.equals("AN")) {
return;
}
cell.setBorderStyle(TgBorderPositionEnum.BOTTOM, BorderStyle.THIN);
});
}
});
// 移除废弃的
excelTemplate.removeSheet(2);
export = excelTemplate.export();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 模拟的数据
public Map<String, Object> getDec() {
Map<String, Object> map = new HashMap<>();
map.put("iEFlag", "进口");
map.put("tiao", "C:\\Users\\doutingjie\\Desktop\\tiao.png");
map.put("er", "C:\\Users\\doutingjie\\Desktop\\er.png");
map.put("seqNo", "232520241254065959");
map.put("entryId", "232520241254065959");
map.put("custName", "昆山海关");
map.put("tradeCoScc", "91320583608277235J");
map.put("tradeName", "好孩子儿童用品有限公司");
map.put("iEPort", "2202");
map.put("iEPortName", "吴淞海关");
map.put("iEDateStr", "20240703");
map.put("dclTimeStr", "20240704");
map.put("manuaNo", "");
map.put("overseasConsignorEname", "U LONG HIGH TECH TEXTILE CO LTD");
map.put("trafMode", "(2)");
map.put("trafModeName", "水路运输");
map.put("yshcName", "GLORY OCEAN/2426N");
map.put("billNo", "JJCKESHT4600591*03");
map.put("goodsPlace", "德祥第五监管区");
map.put("ownerCodeScc", "(91320583608277235J)");
map.put("ownerName", "好孩子儿童用品有限公司");
map.put("tradeMode", "(0110)");
map.put("tradeModeName", "一般贸易");
map.put("cutMode", "(101)");
map.put("cutModeName", "一般征税");
map.put("licenseNo", "");
map.put("despPortCode", "(TWN107)");
map.put("despPortCodeName", "基隆(中国台湾)");
map.put("contrNo", " GB2024061302等");
map.put("tradeAreaCode", "(TWN)");
map.put("tradeAreaCodeName", "中国台湾");
map.put("tradeCountry", "(TWN)");
map.put("tradeCountryName", "中国台湾");
map.put("distinatePort", "(TWN107)");
map.put("distinatePortName", "基隆(中国台湾)");
map.put("entyPortCode", "(310402)");
map.put("entryPortCodeName", "吴淞");
map.put("wrapType", "(99)");
map.put("wrapTypeName", "其他包装");
map.put("packNo", "93");
map.put("grossWet", "2383.68");
map.put("netWt", "2346.48");
map.put("transMode", "(3)");
map.put("transModeName", "FOB");
map.put("fee", "CNY/850/3");
map.put("insur", "000/0.3/1");
map.put("other", "");
map.put("docuCode", "随附单证2:代理报关委托协议(电子);发票;提/运单;企业提供的证明材料;装箱单;合同;原产地证据文件");
map.put("remarkStr", "备注:通关一体化 包装种类其他包装 N/M");
List<Body> list = new ArrayList<>();
list.add(new Body("1", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "34.34千克", "", "63码", "4.3000", "170.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("2", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "598千克", "", "631码", "2.3200", "270.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("3", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "1205.1千克", "", "632码", "1.3000", "370.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("4", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "341.34千克", "", "633码", "2.3000", "470.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("5", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "342.34千克", "", "634码", "3.3000", "570.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("6", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "343.34千克", "", "635码", "4.3000", "670.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("6", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "343.34千克", "", "635码", "4.3000", "670.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("7", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "343.34千克", "", "635码", "4.3000", "670.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("8", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "343.34千克", "", "635码", "4.3000", "670.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("9", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "343.34千克", "", "635码", "4.3000", "670.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("10", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "343.34千克", "", "635码", "4.3000", "670.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("11", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "343.34千克", "", "635码", "4.3000", "670.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("12", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "343.34千克", "", "635码", "4.3000", "670.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("13", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "343.34千克", "", "635码", "4.3000", "670.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("14", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "343.34千克", "", "635码", "4.3000", "670.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("15", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "343.34千克", "", "635码", "4.3000", "670.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("16", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "343.34千克", "", "635码", "4.3000", "670.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("16", "5903209000", "用聚氨基甲酸酯浸渍的其他纺织物", "0|3|弹性PU|100%涤纶|涂布|非双面涂层或包覆|||无品牌 无型号", "343.34千克", "", "635码", "4.3000", "670.90", "美元", "中国台湾", "(TWN)", "中国", "(TWN)", "(32239/320583)昆山/苏州市", "照章征税", "(1)"));
list.add(new Body("", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""));
list.add(new Body("", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""));
list.add(new Body("", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""));
list.add(new Body("", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""));
map.put("body", list);
map.put("jgYxQrName", "否");
map.put("txSyfyQrName", "否");
map.put("djGsQrName", "否");
map.put("zdJgQrName", "否");
map.put("isZName", "否");
map.put("isZName", "否");
map.put("bgryzjh", "23003782");
map.put("agentEpCode", "(91320500608273920M)");
map.put("agentEpCode", "江苏飞力达国际物流股份有限公司");
return map;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91