` 上),并提供健壮、可维护的代码示例。在 HTML 表格中按“每行两列”展示列表数据(例如图书或歌手信息)是一个典型但易出错的场景。许多开发者会错误地将 th:each 直接作用于 <table> 标签(如原代码所示),导致每个数据项生成一个独立表格,不仅语义错误、破坏 DOM 结构,更无法实现真正的多列布局。
正确思路是:让 <tr>(表格行)承担循环职责,并在每轮迭代中主动渲染当前项及其后一项(若存在)。这需要借助 Thymeleaf 的状态变量 iterStat 获取索引,并通过模运算(% 2 == 0)控制仅在偶数索引时创建新行,再用条件判断安全访问下一项。
以下是推荐的生产级实现:
<table class="vocalist-table"> <tr th:each="vocalist, iterStat : ${bookList}" th:if="${iterStat.index % 2 == 0}"> <!-- 第一列:当前项 --> <td class="book-cell"> <div class="bookdiv" th:id="'startButton-' + ${iterStat.index}"> <img src="images/ClosedBook.png" class="closeBook" alt="Closed book"/> <span class="bookName" th:text="${vocalist.vocaName}">Vocalist Name</span> </div> </td> <!-- 第二列:下一项目(仅当存在时渲染) --> <td class="book-cell" th:if="${iterStat.index + 1 < bookList.size()}"> <div class="bookdiv" th:id="'startButton-' + ${iterStat.index + 1}"> <img src="images/ClosedBook.png" class="closeBook" alt="Closed book"/> <span class="bookName" th:text="${bookList[iterStat.index + 1].vocaName}">Next Vocalist</span> </div> </td> </tr></table>
✅ 关键要点说明:
立即学习“前端免费学习笔记(深入)”;
- th:each 必须绑定在 <tr> 上,而非 <table> 或 <td>,确保语义正确且 DOM 层级清晰;
- 使用 iterStat.index % 2 == 0 确保每两组数据共用一行,避免重复渲染;
- th:if="${iterStat.index + 1 < bookList.size()}" 是必需的安全检查,防止索引越界异常(尤其当列表总数为奇数时);
- 为每个 div.bookdiv 动态设置唯一 id(如 startButton-0, startButton-1),便于后续 JavaScript 交互;
- 建议为 <table> 和 <td> 添加语义化 class(如 vocalist-table, book-cell),方便 CSS 样式控制与响应式适配。
⚠️ 注意事项:
- 若需支持响应式(如移动端单列显示),建议优先考虑 CSS Grid 或 Flexbox 替代表格布局,语义更清晰、维护性更强;
- 避免在 <td> 内重复使用 id="startButton" —— ID 在页面中必须唯一,否则违反 HTML 规范,可能导致 JS 选择器失效;
- Thymeleaf 表达式中的 < 需转义为
该方案兼顾兼容性、可读性与健壮性,已在 Spring Boot + Thymeleaf 项目中稳定运行。如需扩展为三列或动态列数,只需调整模运算基数与 <td> 渲染逻辑即可。