利用thymeleaf雙層模板技術(shù)栓撞,制作個(gè)性化報(bào)表

需求背景

通常我們會(huì)碰到一些帶表格的報(bào)表需求,一般是包含一些表格和一些循環(huán)列表的需求切蟋,每個(gè)表格的樣式可以不一樣融柬。
有些公司會(huì)使用一些專業(yè)的報(bào)表軟件鸽素,例如jasperReport隆判,如意報(bào)表等,雖然這種軟件功能很強(qiáng)大俯渤,但是需要一定的學(xué)習(xí)門檻呆细,而且使用中調(diào)整樣式并沒有想象中方便,最重要的是此類模板設(shè)計(jì)八匠、模板導(dǎo)出絮爷、模板上傳都是跟業(yè)務(wù)系統(tǒng)分開的,不能嵌入系統(tǒng)中使用梨树,不能做到在線編輯坑夯,在線預(yù)覽。
為此抡四,我專門研究了thymeleaf來完成此類需求的辦法柜蜈,希望做到報(bào)表上的所有表格都可以通過配置配出來,支持純文本和動(dòng)態(tài)變量指巡,支持圖片淑履,所有表格的樣式可以通過css來隨意配置,支持循環(huán)列表藻雪,支持橫向和縱向合并單元格
為什么選擇thymeleaf而不是選擇freemark等其他模板技術(shù)秘噪,主要基于兩個(gè)原因:
1、spring官方推薦用thymeleaf勉耀,并且springboot有官方適配
2指煎、thymeleaf編寫的模板能直接打開預(yù)覽,不會(huì)像freemark破壞了html原本的格式便斥。

實(shí)現(xiàn)思路

核心思路是:設(shè)計(jì)一個(gè)根模板(root-template.html)至壤,它可以通過設(shè)計(jì)好的數(shù)據(jù)庫配置生成新的具體模板(biz-template.html),并且biz-template.html里面包含具體的業(yè)務(wù)數(shù)據(jù)的變量椭住,這樣就可以結(jié)合業(yè)務(wù)數(shù)據(jù)展示出報(bào)表崇渗。這就是我稱之為雙層模板的原因。
技術(shù)細(xì)節(jié)包含以下幾點(diǎn):
1京郑、根模板(root-template.html)如何設(shè)計(jì)
2、需要用thymeleaf解析html并生成html葫掉,就必須使用API的方式去實(shí)現(xiàn)些举,且html的路徑可配置,否則線上環(huán)境無法生成html到j(luò)ar包里
3俭厚、生成后的html也需要包含thymeleaf的標(biāo)簽户魏,那就需要研究thymeleaf如何生成帶標(biāo)簽的模板
4、樣式和合并單元格如何控制

根模板設(shè)計(jì)

根頁面定好10個(gè)table區(qū)域,每個(gè)區(qū)域都是一樣的邏輯叼丑,判斷table配置的數(shù)據(jù)集是否為空关翎,為空則整個(gè)table不顯示,不為空鸠信,則先循環(huán)table配置的tr信息纵寝,tr里面再循環(huán)配置的td信息。
根模板如下:


image.png

springboot整合thymeleaf星立,并支持api的方式生成html

1爽茴、pom.xml

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
        <relativePath/>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>
    </dependencies>

2、application.properties

server.port=8080

spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.check-template-location=true
spring.thymeleaf.suffix=.html
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.mode=HTML5

report.templates.path=

3绰垂、ThymeleafConfig配置類

@Configuration
@EnableWebMvc
public class ThymeleafConfig extends WebMvcAutoConfiguration {
    @Autowired
    ApplicationContext applicationContext;

    /**
     * eg: E:/data/resources/templates/
     */
    @Value("${report.templates.path}")
    private String reportTemplatesPath;

    @Bean
    public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(templateEngine);
        viewResolver.setCache(false);
        return viewResolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine engine = new SpringTemplateEngine();
//        engine.setEnableSpringELCompiler(true);
        engine.setTemplateResolver(templateResolver());
        return engine;
    }

    @Bean
    public ITemplateResolver templateResolver() {
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setApplicationContext(applicationContext);
        URL resource = this.getClass().getClassLoader().getResource("templates/");       //這里把系統(tǒng)獲取到的Class的path替換為源碼對應(yīng)的Path室奏,這樣修改的時(shí)候就可以動(dòng)態(tài)刷新
        String devResource = resource.getFile().toString().replaceAll("target/classes", "src/main/resources");
        if (reportTemplatesPath!=null&&!"".equals(reportTemplatesPath.trim())){
            devResource=reportTemplatesPath.trim();
        }
        resolver.setPrefix("file://"+devResource);
        resolver.setCacheable(false);//不允許緩存
        resolver.setSuffix(".html");
        return resolver;
    }
}

這樣就可以使用以下代碼來生成html文件了

    @Autowired
    private TemplateEngine templateEngine;

        Context context = new Context();
        context.setVariable("table1", templateData.getTable1());
        FileWriter write = new FileWriter(devResource+"/biz-template.html");
        templateEngine.process("root-template", context, write);

利用thymeleaf生成帶thymeleaf標(biāo)簽的html文件

主要利用兩點(diǎn),

  • 一個(gè)是th:attr

例如劲装,根模板里面:

<div th:attr="'th:text'=${td.value}"></div>

如果td.value=${userName}胧沫,則通過根模板生成后的子模板html代碼是:

<div th:text="${userName}"></div>

看到?jīng)],子模板就也可以再用thymeleaf進(jìn)行解析

這里有兩點(diǎn)特別說明:
1占业、<div th:attr="'th:attr'=${td.value}"></div> 這種方式行不通绒怨,通過th:attr無法增加th:attr標(biāo)簽
2、<div th:attr="'th:text'=${td.value}" th:text="${td.value}"></div> 這種方式也不行纺酸,只能解析后面那個(gè)th:text窖逗,解析結(jié)果如下:
<div>${userName}</div>
那如果又想加th:text標(biāo)簽又想設(shè)置text內(nèi)容怎么辦呢?答案是增加一層div:
<div th:attr="'th:text'=${td.value}" ><div th:text="${td.value}"></div></div>

  • 一個(gè)是th:utext

例如餐蔬,根模板里面:

<div th:utext="${td.value}"></div>

如果td.value是以下字符串<img th:src="${imgUrl}">碎紊,則通過根模板生成后的子模板html代碼是:

<div><img th:src="${imgUrl}"></div>

不錯(cuò)吧,這樣也可以再用thymeleaf解析

樣式和合并單元格問題

使用th:style,th:class,th:rowspan,th:clospan來控制
根模板中要設(shè)置全局自定義樣式變量可以使用如下配置:

<style th:inline="text">
    [[${style}]]
</style>

效果演示

子模板template2


image.png

子模板2填充數(shù)據(jù)后:


image.png

子模板template3
image.png

子模板3填充數(shù)據(jù)后:


image.png

最終的html如果需要轉(zhuǎn)成pdf樊诺,請參考最好的html轉(zhuǎn)pdf工具(wkhtmltopdf)

常用語法

注釋

1仗考、靜態(tài)文件打開可以看到,但是thymeleaf生成后看不到

<!--/*--> 
  <div>
     you can see me only before Thymeleaf processes me!
  </div>
<!--*/-->

2词爬、靜態(tài)文件打開看不到秃嗜,但是thymeleaf生成后看得到

<span>hello!</span>
<!--/*/
  <div th:text="${...}">
    ...
  </div>
/*/-->
<span>goodbye!</span>

3、塊代碼段顿膨,th:block锅锨,可以配合thymeleaf的其他標(biāo)簽去定義代碼塊,例如th:each如果循環(huán)單行<tr>的時(shí)候恋沃,th:each可以放在<tr>標(biāo)簽里必搞,但是如果要循環(huán)多行<tr>的話,則可以配合th:block來使用囊咏,如下:

<table>
  <th:block th:each="user : ${users}">
    <tr>
        <td th:text="${user.login}">...</td>
        <td th:text="${user.name}">...</td>
    </tr>
    <tr>
        <td colspan="2" th:text="${user.address}">...</td>
    </tr>
  </th:block>
</table>

刪除代碼段

以下是Thymeleaf的一個(gè)例子恕洲。我們可以使用th:remove來刪除指定的部分塔橡,這在原型設(shè)計(jì)和調(diào)試的時(shí)候很有用。

  <tr th:remove="all">
    <td>Mild Cinnamon</td>
    <td>1.99</td>
    <td>yes</td>
    <td>
      <span>3</span> comment/s
      <a href="comments.html">view</a>
    </td>
  </tr>

th:remove可接受的值有5個(gè):

  • all: 移除標(biāo)簽和所有子元素
  • body: 移除所有子元素霜第,保留標(biāo)簽
  • tag: 移除標(biāo)簽葛家,保留子元素
  • all-but-first: 保留第一個(gè)子元素,移除所有其他
  • none : 什么也不做泌类。這個(gè)值在動(dòng)態(tài)求值的時(shí)候會(huì)有作用

特別注意點(diǎn)

Thymeleaf中無法利用map.key表達(dá)式獲取到map的鍵值癞谒,特別是使用 th:if="map.key =null" 的時(shí)候,如果map里面不包含key末誓,則立馬報(bào)錯(cuò)扯俱,所以如果要用從map里面取某個(gè)key,則map里面必須要有這個(gè)key值(value為空也必須設(shè)置為null)喇澡,否則會(huì)報(bào)錯(cuò)

參考

Springboot 之 引入Thymeleaf
thymeleaf 基本語法
spring boot(四):thymeleaf使用詳解
Spring Web MVC框架(十二) 使用Thymeleaf
使用Thymeleaf API渲染模板生成靜態(tài)頁面
Spring Boot中Thymeleaf引擎動(dòng)態(tài)刷新
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末迅栅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子晴玖,更是在濱河造成了極大的恐慌读存,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呕屎,死亡現(xiàn)場離奇詭異让簿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)秀睛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門尔当,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蹂安,你說我怎么就攤上這事椭迎。” “怎么了田盈?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵畜号,是天一觀的道長。 經(jīng)常有香客問我允瞧,道長简软,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任述暂,我火速辦了婚禮痹升,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘畦韭。我一直安慰自己视卢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布廊驼。 她就那樣靜靜地躺著据过,像睡著了一般。 火紅的嫁衣襯著肌膚如雪妒挎。 梳的紋絲不亂的頭發(fā)上绳锅,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機(jī)與錄音酝掩,去河邊找鬼鳞芙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛期虾,可吹牛的內(nèi)容都是我干的原朝。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼镶苞,長吁一口氣:“原來是場噩夢啊……” “哼喳坠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起茂蚓,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤壕鹉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后聋涨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晾浴,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年牍白,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了脊凰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茂腥,死狀恐怖狸涌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情础芍,我是刑警寧澤杈抢,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站仑性,受9級特大地震影響惶楼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜诊杆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一歼捐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晨汹,春花似錦豹储、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巩剖。三九已至,卻和暖如春钠怯,著一層夾襖步出監(jiān)牢的瞬間佳魔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工晦炊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鞠鲜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓断国,卻偏偏與公主長得像贤姆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子稳衬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容