当前位置: 首页 > news >正文

别再死记硬背了!用这5个Thymeleaf实战小项目,彻底搞懂SpringBoot模板引擎

5个Thymeleaf实战项目带你玩转SpringBoot模板引擎

如果你已经啃完了Thymeleaf的语法手册,却在真实项目中对着HTML页面手足无措——这太正常了。就像背熟了游泳理论的人第一次下水,真正需要的是在真实场景中建立肌肉记忆。下面这5个精心设计的微型项目,将带你在SpringBoot环境中用Thymeleaf构建一个完整的管理后台,每个功能点都对应着实际开发中的高频需求。

1. 用户档案卡片的动态渲染

先从一个看似简单却暗藏玄机的需求开始:根据用户数据动态生成档案卡片。新建UserController添加测试数据:

@GetMapping("/users") public String showUsers(Model model) { List<User> users = Arrays.asList( new User("张三", 28, "developer", LocalDate.now().minusYears(2)), new User("李四", 35, "manager", LocalDate.now().minusMonths(6)), new User("王五", 22, "intern", LocalDate.now()) ); model.addAttribute("userList", users); return "user-cards"; }

在HTML中,我们用Thymeleaf实现三种关键渲染技术:

<div class="card" th:each="user : ${userList}"> <!-- 条件渲染职位标签 --> <span th:classappend="${user.role == 'manager'} ? 'tag-blue' : ${user.role == 'developer'} ? 'tag-green' : 'tag-gray'" th:text="${user.role}"></span> <!-- 日期格式化 --> <p>入职时间:[[${#temporals.format(user.joinDate, 'yyyy年MM月dd日')}]]</p> <!-- 复合属性展示 --> <div th:text="|${user.name} (${user.age}岁)|"></div> </div>

避坑指南

  • 当遇到Could not parse as expression错误时,检查是否漏掉了|管道符
  • 日期格式化前确保Model中的是java.time对象而非字符串
  • th:classappend比直接操作class属性更安全

2. 智能数据表格与筛选器

管理后台最常见的功能非数据表格莫属。我们升级用户列表为可交互表格:

<!-- 筛选表单 --> <form th:action="@{/users}" method="get"> <input type="text" name="nameFilter" th:value="${param.nameFilter}"> <select name="roleFilter" th:value="${param.roleFilter}"> <option value="">所有角色</option> <option th:each="role : ${allRoles}" th:value="${role}" th:text="${role}"></option> </select> </form> <!-- 动态表格 --> <table> <tr th:each="user, stat : ${filteredUsers}"> <td th:text="${stat.count}"></td> <td th:text="${user.name}"></td> <td> <span th:switch="${user.role}"> <span th:case="'admin'" class="icon-shield"></span> <span th:case="'manager'" class="icon-star"></span> <span th:case="*" class="icon-user"></span> </span> </td> </tr> </table>

后端控制器需要处理筛选参数:

@GetMapping("/users") public String filterUsers( @RequestParam(required = false) String nameFilter, @RequestParam(required = false) String roleFilter, Model model) { List<User> filtered = userService.findByFilters(nameFilter, roleFilter); model.addAttribute("filteredUsers", filtered); return "user-table"; }

高级技巧

  • 使用th:with局部变量简化重复表达式
  • th:attr动态设置自定义数据属性
  • 通过#lists.isEmpty()判断空列表状态

3. 多步骤表单与数据绑定

实现一个用户注册流程,展示Thymeleaf的表单绑定能力:

<!-- 第一步:基本信息 --> <form th:action="@{/register}" th:object="${userForm}" method="post"> <input type="text" th:field="*{name}" placeholder="姓名"> <div th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div> <input type="email" th:field="*{email}"> <div th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></div> </form> <!-- 第二步:偏好设置(同一表单对象) --> <form th:action="@{/register/step2}" th:object="${userForm}" method="post"> <select th:field="*{preferredLanguage}"> <option th:each="lang : ${languages}" th:value="${lang.code}" th:text="${lang.name}"></option> </select> <div th:each="topic : ${allTopics}"> <input type="checkbox" th:field="*{interestedTopics}" th:value="${topic.id}"> <label th:text="${topic.name}"></label> </div> </form>

对应的Spring控制器:

@PostMapping("/register") public String processStep1(@Valid UserForm form, BindingResult result) { if (result.hasErrors()) { return "register-step1"; } return "redirect:/register/step2"; } @GetMapping("/register/step2") public String showStep2(Model model) { model.addAttribute("languages", languageService.getAll()); model.addAttribute("allTopics", topicService.getTopics()); return "register-step2"; }

关键点

  • th:field自动绑定到表单对象属性
  • th:errors显示验证错误信息
  • 多步骤表单保持相同th:object引用
  • 复选框绑定会自动处理数组转换

4. 动态导航与权限控制

根据用户权限动态生成侧边栏菜单:

<div class="sidebar" th:with="user=${session.user}"> <div th:each="menu : ${allMenus}" th:if="${#authorization.expression(menu.permission)}"> <a th:href="@{${menu.url}}" th:text="${menu.title}"></a> <!-- 嵌套子菜单 --> <div th:if="${menu.children}" class="submenu"> <a th:each="sub : ${menu.children}" th:href="@{${sub.url}}" th:text="${sub.title}" th:if="${#authorization.expression(sub.permission)}"></a> </div> </div> </div>

安全配置类需要配合使用:

@Configuration public class SecurityConfig { @Bean public SpringSecurityDialect securityDialect() { return new SpringSecurityDialect(); } }

实现细节

  • 使用th:with存储频繁访问的会话数据
  • Spring Security集成需要添加依赖:
    <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency>
  • 菜单数据建议缓存优化性能

5. 实时搜索与AJAX集成

最后我们实现一个无刷新搜索功能,展示Thymeleaf与现代前端技术的融合:

<!-- 搜索框 --> <input type="text" id="searchInput" placeholder="实时搜索..."> <!-- 结果容器 --> <div id="liveResults"> <!-- 初始状态显示全部 --> <div th:replace="~{fragments/user-list :: ${defaultUsers}}"></div> </div> <script th:inline="javascript"> document.getElementById('searchInput').addEventListener('input', function() { fetch('/api/users/search?term=' + encodeURIComponent(this.value)) .then(response => response.text()) .then(html => { document.getElementById('liveResults').innerHTML = html; }); }); </script>

对应的片段模板fragments/user-list.html

<div th:fragment="user-list(users)"> <div th:each="user : ${users}" class="search-result"> <h3 th:text="${user.name}"></h3> <p th:text="${user.email}"></p> </div> <!-- 空状态提示 --> <div th:if="${#lists.isEmpty(users)}" class="empty-tip"> 没有找到匹配的用户 </div> </div>

后端控制器提供两种响应:

@GetMapping("/api/users/search") public String searchUsers(@RequestParam String term, Model model) { model.addAttribute("users", userService.search(term)); return "fragments/user-list :: user-list"; }

性能优化建议

  • 添加防抖(debounce)减少请求频率
  • 使用th:remove优化初始加载性能
  • 考虑WebSocket实现真正实时更新

经过这五个项目的实战,你会发现Thymeleaf的th:属性就像乐高积木的连接件,让静态HTML获得了动态组装的能力。这种服务端渲染的方式在现代前端框架盛行的今天依然有其不可替代的优势——特别是当你需要快速交付、SEO友好或与Spring生态深度集成时。

http://www.cnnetsun.cn/news/1562038.html

相关文章:

  • 程序实现多台仪器无线组网,数据互通,颠覆单台独立工作模式,实现协同测量。
  • 漫画脸描述生成保姆级教程:从Docker Hub拉取镜像到生成首个角色
  • 避坑指南:为什么你的 Docker 容器里总有 Chrome 僵尸进程?深入解析进程托管机制
  • TensorFlow Playground新手必看:5分钟搞懂神经网络可视化训练(附实战截图)
  • 5步释放华硕笔记本潜能:轻量级开源工具GHelper的实战优化指南
  • 别再用传统方式渲染Spine了!一份给独立游戏开发者的GPU动画烘焙与资源管理指南
  • 突破式3步实现:用MOOTDX构建零成本金融数据获取引擎
  • 用Excel自动计算软考挣值管理:从PV/EV到TCPI的模板制作教程
  • Win10+Ubuntu双系统翻车?手把手教你从GRUB急救模式恢复Windows引导
  • 3大创新机制:构建零配置智能体通信系统的完整方案
  • OpenClaw技能扩展指南:安装GLM-4.7-Flash专用插件提升能力
  • Arch Linux 新手必看:Pacman 包管理器的 10 个实用技巧(含镜像加速配置)
  • 别再死记硬背了!用Keras跑个Demo,5分钟搞懂Epoch、Batch Size和Iterations的关系
  • C++实战:手把手教你用DWA算法实现机器人避障(附完整代码)
  • 解决curl静态库链接错误:__imp__CertCloseStore@8等符号未定义问题
  • 独立站SEO与电商站点SEO有什么区别
  • 从梯度流到记忆门:RNN长程依赖问题的演进与实战破解
  • 如何对seo关键词组合进行持续优化和迭代_针对不同目标用户的seo关键词组合应该如何选择
  • foobox-cn终极美化方案:打造专业级音乐播放器界面
  • 解决企业知识孤岛挑战:Outline多平台文档迁移架构与技术实现方案
  • 罗技鼠标PUBG压枪宏:三步实现稳定射击的终极指南
  • QGroundControl(QGC)核心功能与行业应用深度解析
  • 某东H5ST参数逆向避坑指南:定值处理、动态Key与SHA256拼接的那些坑
  • 抖音批量下载器终极指南:5分钟搭建个人视频资源库
  • LongCat-Image-Editn实战体验:上传图片+输入中文,3步完成精准图像编辑
  • 美团智能抢券助手完整指南:如何实现天天神券自动抢券与签到
  • 如何高效部署Uvicorn Python ASGI应用:专业实战指南
  • 告别英文烦恼:3分钟免费解锁Axure RP中文界面完整指南
  • 阿里云RUM SDK:破解移动端网络性能监控难题
  • 解码音频封装格式:从元数据到音质差异的全面解析