Thymeleaf案例

wen java案例 2

本文目录导读:

Thymeleaf案例

  1. 项目结构
  2. pom.xml配置
  3. 模型类
  4. 控制器
  5. 模板页面
  6. Main Application
  7. application.yml
  8. 运行测试
  9. 功能亮点

我来为您提供一个完整的Thymeleaf案例,包括Spring Boot集成和常用功能展示。

项目结构

thymeleaf-demo/
├── pom.xml
├── src/main/java/com/example/thymeleaf/
│   ├── ThymeleafApplication.java
│   ├── controller/
│   │   ├── UserController.java
│   │   └── ProductController.java
│   └── model/
│       ├── User.java
│       └── Product.java
└── src/main/resources/
    ├── application.yml
    └── templates/
        ├── index.html
        ├── user/
        │   └── user-list.html
        └── product/
            └── product-list.html

pom.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>thymeleaf-demo</artifactId>
    <version>1.0.0</version>
    <name>thymeleaf-demo</name>
    <description>Thymeleaf示例项目</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

模型类

User.java

package com.example.thymeleaf.model;
import java.time.LocalDateTime;
public class User {
    private Long id;
    private String username;
    private String email;
    private Integer age;
    private String role;
    private boolean active;
    private LocalDateTime createTime;
    // 构造器
    public User() {}
    public User(Long id, String username, String email, Integer age, 
                String role, boolean active, LocalDateTime createTime) {
        this.id = id;
        this.username = username;
        this.email = email;
        this.age = age;
        this.role = role;
        this.active = active;
        this.createTime = createTime;
    }
    // Getter和Setter
    // ... 省略的getter/setter方法
}

Product.java

package com.example.thymeleaf.model;
import java.math.BigDecimal;
public class Product {
    private Long id;
    private String name;
    private String category;
    private BigDecimal price;
    private Integer stock;
    private String description;
    public Product() {}
    public Product(Long id, String name, String category, 
                   BigDecimal price, Integer stock, String description) {
        this.id = id;
        this.name = name;
        this.category = category;
        this.price = price;
        this.stock = stock;
        this.description = description;
    }
    // Getter和Setter
    // ... 省略的getter/setter方法
}

控制器

UserController.java

package com.example.thymeleaf.controller;
import com.example.thymeleaf.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Controller
@RequestMapping("/users")
public class UserController {
    // 模拟数据
    private static List<User> users = new ArrayList<>(Arrays.asList(
        new User(1L, "张三", "zhangsan@example.com", 25, "管理员", true, LocalDateTime.now().minusDays(30)),
        new User(2L, "李四", "lisi@example.com", 30, "用户", true, LocalDateTime.now().minusDays(20)),
        new User(3L, "王五", "wangwu@example.com", 22, "用户", false, LocalDateTime.now().minusDays(10)),
        new User(4L, "赵六", "zhaoliu@example.com", 35, "访客", true, LocalDateTime.now().minusDays(5)),
        new User(5L, "钱七", "qianqi@example.com", 28, "用户", false, LocalDateTime.now())
    ));
    // 用户列表
    @GetMapping
    public String listUsers(Model model) {
        model.addAttribute("users", users);
        model.addAttribute("totalUsers", users.size());
        model.addAttribute("activeUsers", users.stream().filter(User::isActive).count());
        return "user/user-list";
    }
    // 用户详情
    @GetMapping("/{id}")
    public String userDetail(@PathVariable Long id, Model model) {
        User user = users.stream()
                .filter(u -> u.getId().equals(id))
                .findFirst()
                .orElse(null);
        model.addAttribute("user", user);
        return "user/user-detail";
    }
    // 添加用户表单
    @GetMapping("/add")
    public String showAddForm(Model model) {
        model.addAttribute("user", new User());
        model.addAttribute("roles", Arrays.asList("管理员", "用户", "访客"));
        return "user/user-form";
    }
    // 保存用户
    @PostMapping
    public String saveUser(@ModelAttribute User user) {
        user.setId((long) (users.size() + 1));
        user.setCreateTime(LocalDateTime.now());
        users.add(user);
        return "redirect:/users";
    }
}

ProductController.java

package com.example.thymeleaf.controller;
import com.example.thymeleaf.model.Product;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@Controller
@RequestMapping("/products")
public class ProductController {
    private static List<Product> products = new ArrayList<>();
    static {
        products.add(new Product(1L, "笔记本电脑", "电子产品", new BigDecimal("5999.00"), 100, "高性能便携设备"));
        products.add(new Product(2L, "无线鼠标", "配件", new BigDecimal("99.50"), 500, "无线连接设备"));
        products.add(new Product(3L, "机械键盘", "配件", new BigDecimal("399.00"), 200, "专业游戏键盘"));
        products.add(new Product(4L, "显示器", "电子产品", new BigDecimal("1599.99"), 50, "27英寸4K显示器"));
        products.add(new Product(5L, "USB扩展坞", "配件", new BigDecimal("199.00"), 300, "多功能接口扩展"));
    }
    @GetMapping
    public String productList(Model model) {
        model.addAttribute("products", products);
        // 计算统计数据
        BigDecimal totalValue = products.stream()
                .map(p -> p.getPrice().multiply(BigDecimal.valueOf(p.getStock())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        model.addAttribute("totalProducts", products.size());
        model.addAttribute("totalValue", totalValue);
        return "product/product-list";
    }
}

模板页面

index.html

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">Thymeleaf案例演示</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: 'Microsoft YaHei', Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 10px;
            padding: 30px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.1);
        }
        h1 {
            color: #333;
            margin-bottom: 20px;
            text-align: center;
        }
        .menu {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
            margin-top: 30px;
        }
        .menu-item {
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            padding: 20px;
            border-radius: 8px;
            text-decoration: none;
            transition: transform 0.3s;
            text-align: center;
        }
        .menu-item:hover {
            transform: translateY(-5px);
            box-shadow: 0 5px 20px rgba(102, 126, 234, 0.5);
        }
        .menu-item h3 {
            font-size: 24px;
            margin-bottom: 10px;
        }
        .menu-item p {
            opacity: 0.9;
            font-size: 14px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Thymeleaf 案例演示</h1>
        <p style="text-align: center; color: #666; margin-bottom: 30px;">
            本案例展示了 Thymeleaf 的主要功能:条件判断、循环、表达式、格式化等
        </p>
        <div class="menu">
            <a th:href="@{/users}" class="menu-item">
                <h3>👥 用户管理</h3>
                <p>浏览用户列表,查看详情,操作示例</p>
            </a>
            <a th:href="@{/products}" class="menu-item">
                <h3>📦 商品管理</h3>
                <p>查看商品信息,计算总价值等</p>
            </a>
        </div>
    </div>
</body>
</html>

user-list.html

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">用户列表</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: 'Microsoft YaHei', Arial, sans-serif;
            background: #f5f5f5;
            padding: 20px;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 10px;
            box-shadow: 0 5px 20px rgba(0,0,0,0.1);
            padding: 30px;
        }
        h1 {
            color: #333;
            margin-bottom: 20px;
        }
        .stats {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 20px;
            margin-bottom: 30px;
        }
        .stat-card {
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            padding: 20px;
            border-radius: 8px;
            text-align: center;
        }
        .stat-card h2 {
            font-size: 36px;
            margin-bottom: 5px;
        }
        .stat-card p {
            opacity: 0.9;
        }
        .add-btn {
            display: inline-block;
            background: #4CAF50;
            color: white;
            padding: 10px 20px;
            border-radius: 5px;
            text-decoration: none;
            margin-bottom: 20px;
            transition: background 0.3s;
        }
        .add-btn:hover {
            background: #45a049;
        }
        table {
            width: 100%;
            border-collapse: collapse;
        }
        th, td {
            padding: 12px;
            text-align: left;
            border-bottom: 1px solid #ddd;
        }
        th {
            background-color: #f2f2f2;
            font-weight: bold;
            color: #333;
        }
        tr:hover {
            background-color: #f8f9fa;
        }
        .badge {
            padding: 4px 8px;
            border-radius: 4px;
            font-size: 12px;
            font-weight: bold;
        }
        .badge-success {
            background-color: #4CAF50;
            color: white;
        }
        .badge-danger {
            background-color: #f44336;
            color: white;
        }
        .btn {
            padding: 6px 12px;
            margin-right: 5px;
            border-radius: 4px;
            text-decoration: none;
            font-size: 13px;
            display: inline-block;
        }
        .btn-info {
            background-color: #2196F3;
            color: white;
        }
        .btn-danger {
            background-color: #f44336;
            color: white;
        }
        .back-link {
            display: inline-block;
            margin-bottom: 20px;
            color: #667eea;
            text-decoration: none;
        }
        .empty-state {
            text-align: center;
            padding: 40px;
            color: #999;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>👥 用户列表</h1>
        <a th:href="@{/}" class="back-link">← 返回首页</a>
        <!-- 统计卡片 -->
        <div class="stats">
            <div class="stat-card">
                <h2 th:text="${totalUsers}">0</h2>
                <p>用户总数</p>
            </div>
            <div class="stat-card">
                <h2 th:text="${activeUsers}">0</h2>
                <p>活跃用户</p>
            </div>
            <div class="stat-card">
                <h2 th:text="${totalUsers - activeUsers}">0</h2>
                <p>非活跃用户</p>
            </div>
        </div>
        <a th:href="@{/users/add}" class="add-btn">➕ 添加用户</a>
        <!-- 用户表格 -->
        <table>
            <thead>
                <tr>
                    <th>ID</th>
                    <th>用户名</th>
                    <th>邮箱</th>
                    <th>年龄</th>
                    <th>角色</th>
                    <th>状态</th>
                    <th>创建时间</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <!-- 条件判断 -->
                <tr th:if="${!#lists.isEmpty(users)}" th:each="user : ${users}" 
                    th:classappend="${userStat.count % 2 == 0 ? 'even' : 'odd'}">
                    <td th:text="${user.id}">1</td>
                    <td th:text="${user.username}">张三</td>
                    <td th:text="${user.email}">email@example.com</td>
                    <td th:text="${user.age}">25</td>
                    <!-- 条件判断选择器 -->
                    <td>
                        <span th:switch="${user.role}" class="badge" 
                              th:classappend="${user.role == '管理员'} ? 'badge-danger' : ''">
                            <span th:case="'管理员'" th:text="${user.role}">管理员</span>
                            <span th:case="'用户'" th:text="${user.role}">用户</span>
                            <span th:case="*" th:text="${user.role}">其他</span>
                        </span>
                    </td>
                    <td>
                        <!-- if条件判断 -->
                        <span th:if="${user.active}" class="badge badge-success">活跃</span>
                        <span th:if="${!user.active}" class="badge badge-danger">禁用</span>
                    </td>
                    <!-- 日期格式化 -->
                    <td th:text="${#temporals.format(user.createTime, 'yyyy-MM-dd HH:mm')}">2024-01-01 10:00</td>
                    <td>
                        <a th:href="@{/users/{id}(id=${user.id})}" class="btn btn-info">查看</a>
                        <a th:href="@{/users/delete/{id}(id=${user.id})}" class="btn btn-danger">删除</a>
                    </td>
                </tr>
                <!-- 空状态 -->
                <tr th:if="${#lists.isEmpty(users)}">
                    <td colspan="8" class="empty-state">
                        <p style="font-size: 18px; margin-bottom: 10px;">暂无用户数据</p>
                        <p>点击"添加用户"按钮创建第一个用户</p>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
</html>

product-list.html

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">商品列表</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: 'Microsoft YaHei', Arial, sans-serif;
            background: #f5f5f5;
            padding: 20px;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 10px;
            box-shadow: 0 5px 20px rgba(0,0,0,0.1);
            padding: 30px;
        }
        h1 {
            color: #333;
            margin-bottom: 30px;
        }
        .back-link {
            display: inline-block;
            margin-bottom: 20px;
            color: #667eea;
            text-decoration: none;
        }
        .product-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
            gap: 20px;
        }
        .product-card {
            border: 1px solid #ddd;
            border-radius: 8px;
            padding: 20px;
            transition: transform 0.3s, box-shadow 0.3s;
        }
        .product-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 5px 20px rgba(0,0,0,0.1);
        }
        .product-card .category {
            display: inline-block;
            padding: 4px 10px;
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            border-radius: 20px;
            font-size: 12px;
            margin-bottom: 10px;
        }
        .product-card h3 {
            font-size: 20px;
            margin-bottom: 10px;
            color: #333;
        }
        .product-card .price {
            font-size: 24px;
            color: #e74c3c;
            font-weight: bold;
            margin: 10px 0;
        }
        .product-card .stock {
            color: #666;
            margin-bottom: 10px;
        }
        .product-card .description {
            color: #888;
            font-size: 14px;
            line-height: 1.6;
        }
        .stats-bar {
            display: flex;
            gap: 30px;
            margin-bottom: 30px;
            padding: 20px;
            background: #f8f9fa;
            border-radius: 8px;
        }
        .stat {
            text-align: center;
        }
        .stat-value {
            font-size: 28px;
            font-weight: bold;
            color: #667eea;
        }
        .stat-label {
            color: #666;
            font-size: 14px;
        }
        .stock-low {
            color: #e74c3c;
            font-weight: bold;
        }
        .stock-normal {
            color: #2ecc71;
        }
        .stock-high {
            color: #3498db;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>📦 商品列表</h1>
        <a th:href="@{/}" class="back-link">← 返回首页</a>
        <!-- 统计信息 -->
        <div class="stats-bar">
            <div class="stat">
                <div class="stat-value" th:text="${totalProducts}">0</div>
                <div class="stat-label">商品总数</div>
            </div>
            <div class="stat">
                <div class="stat-value" th:text="${#numbers.formatDecimal(totalValue, 1, 2)}">0.00</div>
                <div class="stat-label">总价值(元)</div>
            </div>
        </div>
        <!-- 商品网格 -->
        <div class="product-grid">
            <div class="product-card" th:each="product : ${products}" 
                 th:classappend="${product.stock > 100 ? 'high-stock' : (product.stock > 50 ? 'normal-stock' : 'low-stock')}">
                <span class="category" th:text="${product.category}">分类</span>
                <h3 th:text="${product.name}">商品名称</h3>
                <div class="price" th:text="${#numbers.formatDecimal(product.price, 1, 2)} + ' 元'">¥0.00</div>
                <div class="stock" th:classappend="${product.stock < 100 ? 'stock-low' : 'stock-normal'}">
                    库存:<span th:text="${product.stock}">0</span> 件
                </div>
                <p class="description" th:text="${#strings.abbreviate(product.description, 50)}">商品描述</p>
                <!-- 库存状态条件显示 -->
                <div style="margin-top: 10px;">
                    <span th:if="${product.stock <= 0}" style="color: #e74c3c; font-weight: bold;">🚫 已售罄</span>
                    <span th:if="${product.stock > 0 and product.stock <= 10}" style="color: #e67e22;">⚠️ 库存紧张</span>
                    <span th:if="${product.stock > 10 and product.stock <= 50}" style="color: #27ae60;">✅ 库存充足</span>
                    <span th:if="${product.stock > 50}" style="color: #3498db;">📊 库存充裕</span>
                </div>
            </div>
        </div>
        <!-- 表达式运算示例 -->
        <div style="margin-top: 30px; padding: 20px; background: #f8f9fa; border-radius: 8px;">
            <h3 style="margin-bottom: 15px;">表达式运算示例:</h3>
            <ul style="list-style: none;">
                <li th:each="product : ${products}">
                    <span th:text="${product.name}">商品名</span> × 
                    <span th:text="${product.stock}">库存</span> = 
                    <span th:text="${#numbers.formatDecimal(product.price * product.stock, 1, 2)}">总价</span> 元
                </li>
            </ul>
        </div>
    </div>
</body>
</html>

Main Application

package com.example.thymeleaf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ThymeleafApplication {
    public static void main(String[] args) {
        SpringApplication.run(ThymeleafApplication.class, args);
    }
}

application.yml

server:
  port: 8080
spring:
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    cache: false
    encoding: UTF-8
    mode: HTML

运行测试

  1. 运行 ThymeleafApplication 主类
  2. 访问 http://localhost:8080 查看首页
  3. 点击"用户管理"查看用户列表
  4. 点击"商品管理"查看商品列表

功能亮点

  1. 条件判断:使用 th:if, th:unless, th:switch, th:case
  2. 循环遍历:使用 th:each 遍历集合
  3. 属性操作th:href, th:text, th:classappend
  4. 内联表达式:, ,
  5. 日期格式化:使用 #temporals.format()
  6. 数字格式化:使用 #numbers.formatDecimal()
  7. 字符串操作:使用 #strings.abbreviate()
  8. URL链接:使用 th:href="@{/users/{id}(id=${user.id})}"
  9. 样式动态绑定:基于条件的CSS类切换
  10. 统计聚合:在控制器中计算统计数据

这个案例涵盖了Thymeleaf在Spring Boot项目中的主要功能和使用场景,非常适合学习和参考,您可以根据需要扩展更多功能。

抱歉,评论功能暂时关闭!