引子
之前自己撸代码的时候,需要使用邮件发送报表。当然,我曾在项目中见过有大佬直接使用StringBuilder拼接除了一整个表格的HTML。这种牛逼而又Low B的代码,实在是鄙视。针对这个需求,我选择的方式是,使用Thymeleaf将数据渲染到我们构建好的HTML模板上,同时拿到渲染完毕后HTML文档的字符串,最后将其嵌入邮件中进行发送。
通过查询资料,网上对于Thymeleaf的复杂表格构建相关的文章是少之又少。这里呢,我就整理下我的处理方式,记录下来并和大家分享。
注意事项
针对HTML模板中的代码,如果有看不懂的地方,请各位多看几遍,尝试着理解!其实很简单!
结果预览:
最终结果如图所示:在如图所示的表格中,价格趋势、APP名称分别占据 多行,瑰红的分隔行 占据多列。
示例代码
在这里呢,我给出部分关键代码。附件信息中我给出了完整代码,有需要的同学可以去下载。
首先,我们构建出最终需要向HTML模板上渲染的数据实体。Lombok注解不解释,有疑惑的同学请自行谷歌。
@Data
@Builder
public class AppPriceInfoVO {
private Integer upNum;
private Integer downNum;
private String mailTime;
private Map<String, List<AppInfo>> upPriceMap;
private Map<String, List<AppInfo>> downPriceMap;
}
然后是上面的AppInfo类的信息,里面封装了一些我们最终会展示到的App的一些信息:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AppInfo {
private Long id;
private Long appId;
private String name;
private String price;
private String language;
private String version;
private String url;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private Date updateTime;
}
然后,最重要的部分来了,我们的HTML模板,这里,我只给出数据渲染部分的代码:
<!--/*@thymesVar id="appPriceInfoVO" type="com.zereao.apphunter.pojo.vo.AppPriceInfoVO"*/-->
<div th:remove="tag" th:each="downPriceEntry,stats:${appPriceInfoVO.downPriceMap}"
th:with="appName = ${downPriceEntry.key}, appChangeNum = ${downPriceEntry.value.size()},
appInfo0 = ${downPriceEntry.value.get(0)}, downPriceList = ${downPriceEntry.value}">
<tr th:if="${appPriceInfoVO.downNum}>0">
<td class="btbg1" th:text="价格下降" th:rowspan="${appPriceInfoVO.downNum}" th:if="${stats.first}"></td>
<td th:text="${appName}" th:rowspan="${appChangeNum}"
th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'"></td>
<td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${appInfo0.price}"></td>
<td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${appInfo0.version}"></td>
<td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${appInfo0.createTime}"></td>
<td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${appInfo0.language}"></td>
<td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'">
<a th:href="${appInfo0.url}" target="_blank" th:text="${appInfo0.name}"></a>
</td>
</tr>
<tr th:each="downPriceAppInfo,stat : ${downPriceList}" th:if="${!stat.first}">
<td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${downPriceAppInfo.price}"></td>
<td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${downPriceAppInfo.version}"></td>
<td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${downPriceAppInfo.createTime}"></td>
<td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${downPriceAppInfo.language}"></td>
<td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'">
<a th:href="${downPriceAppInfo.url}" target="_blank" th:text="${downPriceAppInfo.name}"></a>
</td>
</tr>
</div>
如图,上面是 价格下降 部分的APP的信息的HTML模板。
我的理解是,着重理解 <div th:remove="tag" xxx></div> 这个地方,如果有什么不明白的,多看几遍。
解释一下部分标签的含义:
- th:remove:在Thymeleaf模板引擎处理到这个地方的时候,会自动移除该标签行,不会移除其子标签。
- th:each:迭代集合或者数组;
- th:with:临时变量的声明
- 其他标签,可以查阅官方文档。
附件信息
在本文正文中,我只给出了部分关键代码,完整有完整代码需求的同学,可以选择下载下面的附件实例代码。附件中是完整的示例代码,GitHub是完整的项目,示例代码属于项目的一部分。
文中实例完整代码:spring-boot-thymeleaf-sample.zip
GitHub:GitHub地址
参考文档
1、https://stackoverflow.com/questions/35999679/thymeleaf-table-issues-with-rowspan-1-order-n-articles
2、https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html