Contents

Thymeleaf布局踩坑

Thymeleaf模板引擎第一次使用,之前用过的都是freemarker,结合Spring boot记录一下初次使用时遇到的一些小坑,主要是路径和包名的一些问题。下面将记录使用thymeleaf的layout布局方式展现web内容的整个过程。

Build with Maven

Thymeleaf有两种布局的方式,第一种是使用 th:include 和 th:replace这种的形式,第二种是使用layout:decorate这种。

本文使用第二种,需要引入thymeleaf-layout-dialect这个包,否则layout:decorate这种布局方式不会生效(踩到的第一个坑)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<dependency>
    <groupId> nz.net.ultraq.thymeleaf</groupId>
    <artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>

ViewModel

这里对html模板进行了一个封装,使其可以更加灵活方便地使用,就是一个POJO类,对Template中变动的内容进行定义,在提供一个返回ModelAndView对象的方法。

package me.icharm.orange.ViewModel;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.springframework.web.servlet.ModelAndView;

import java.util.Map;

/**
 * @author elias
 * @email [email protected]
 * @date 2018/11/21 17:40
 */
@Data
@Slf4j
public class WeuiResultPage {

    /**
     * 视图模板路径
     */
    public String path = "weui/result_fragment";

    /**
     * 图标
     */
    private String icon;

    /**
     * 标题
     */
    private String title;

    /**
     * 内容
     */
    private String content;

    /**
     * 绿色按钮名称
     */
    private String btnPrimary;

    /**
     * 白色按钮名称
     */
    private String btnDefault;

    /**
     * 绿色按钮事件
     */
    private String btnPrimaryAction;

    /**
     * 白色按钮事件
     */
    private String btnDefaultAction;

    /**
     * This object convert to ModelAndView object.
     *
     * @return ModelMap
     */
    public ModelAndView modelAndView() {
        try {
            ModelAndView mv = new ModelAndView(this.path);
            // Object to map 
            Map<String, String> map = BeanUtils.describe(this);
            mv.addAllObjects(map);
            return mv;
        } catch (Exception e) {
            log.error("WeuiResultPage ViewModel Object convert to map error: " + e);
            return null;
        }
    }
}
</pre>

[/mdx_fold]

## Controller

Controller中很简单new 一个WeuiResultPage对象返回其对应的ModeAndView对象  
[mdx_fold title="CommonController.java"]  
<!--?prettify linenums=true?-->

<pre class="prettyprint">import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import me.icharm.orange.ViewModel.WeuiResultPage;

/**
* @author elias
* @email [email protected]
* @date 2018/8/20 17:27
*/
@Controller
@RequestMapping("/common")
public class CommonController extends RootController {

    @RequestMapping("/result")
    public ModelAndView result() {
        WeuiResultPage view = new WeuiResultPage();
        view.setIcon("success");
        view.setTitle("授权成功");
        view.setBtnPrimary("确定");
        view.setBtnPrimaryAction("wx.closeWindow();"); //使用微信JS-SDK提供的API关闭当前的网页窗口
        view.setBtnDefault("取消");
        view.setBtnDefaultAction("wx.closeWindow();");
        return view.modelAndView();
    }
}

View

layout:decorate布局的基本方式:先写一个布局页。

然后在每一个具体页面中通过layout:decorate指定使用的布局页(按照我的理解就是继承),可以选择性重写布局页中的某些fragment(片段)。

<!DOCTYPE html>
<html lang="zh-cmn-Hans"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<!-- 上面xmlns:th、xmlns:layout很重要,分别为下面的th:href, layout:fragment等标签元素指定所属的命名空间 -->
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0,viewport-fit=cover">
    <title>Layout</title>
    <!-- 引入Css、js、image等静态资源时,路径是相对于resources/static,而且前面要带上/ -->
    <link rel="stylesheet" th:href="@{/style/weui.min.css}"/>
    <!--<link rel="stylesheet" href="./example.css"/>-->
</head>
<body ontouchstart>
<div class="weui-toptips weui-toptips_warn js_tooltips">错误提示</div>

<div class="container" id="container">
    <!-- 定义一个可以在子页面重写的fragment -->
    <div layout:fragment="content"></div>
</div>

<script th:src="@{/js/zepto.min.js}"></script>
<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script src="https://res.wx.qq.com/open/libs/weuijs/1.0.0/weui.min.js"></script>
<!--<script src="js/example.js"></script>-->
<script>
    // 下面是配置微信的JS-SDk,方便在微信里面使用一个特定的Api
    wx.config({
        debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
        appId: '', // 必填,公众号的唯一标识
        timestamp: '', // 必填,生成签名的时间戳
        nonceStr: '', // 必填,生成签名的随机串
        signature: '',// 必填,签名,见附录1
        jsApiList: ['closeWindow'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
    });
</script>
</body>
</html>

下面是一个具体的子页面:值得注意的是第四行的layout:decorate的路径写法,和静态资源的写法类似,不过这里是相对于resources/templates, 在thymeleaf3中推荐使用~{}来选择布局页面。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{/weui/layout}">
<head>
    <title th:text="${title}">结果页</title>
    <!--每页单独引入的文件在这写↓-->
    <!--<script th:src="@{../js/alert.js}"></script>-->
</head>
<body>
<div layout:fragment="content">
    <!--在这里写入页面内容↓-->
<div id="content">
    <div class="page">
        <div class="weui-msg">
            <div class="weui-msg__icon-area"><i th:class="'weui-icon_msg weui-icon-'+${icon}" ></i></div>
            <div class="weui-msg__text-area">
                <h2 class="weui-msg__title" th:text="${title}">操作成功</h2>
                <p class="weui-msg__desc" th:text="${content}"></p>
            </div>
            <div class="weui-msg__opr-area">
                <p class="weui-btn-area">
                    <a th:href="'javascript:'+${btnPrimaryAction}" class="weui-btn weui-btn_primary" th:text="${btnPrimary}">确定</a>
                    <a th:href="'javascript:'+${btnDefaultAction}" class="weui-btn weui-btn_default" th:text="${btnDefault}">取消</a>
                </p>
            </div>
            <div class="weui-msg__extra-area">
                <div class="weui-footer">
                    <p class="weui-footer__links">
                        <a href="javascript:void(0);" class="weui-footer__link">ICHARM</a>
                    </p>
                    <p class="weui-footer__text">Copyright &copy; 2018 icharm.me</p>
                </div>
            </div>
        </div>
    </div>
</div>
</div>
</body>
</html>

页面效果图:

/result.png
1

Reference

thymeleaf-layout-dialect

Standard URL Syntax