如何在Thymeleaf中按课程分组实现Bootstrap手风琴展示飞盘高尔夫回合
解决方案:按课程分组实现Bootstrap手风琴
你需要将数据处理成**「课程-回合」的嵌套结构**,否则平铺的回合列表会导致每个回合生成独立的手风琴项,无法实现「点击课程展开所有对应回合」的需求。以下是具体修改步骤:
1. 后端控制器修改:对回合按课程分组
在控制器中把平铺的Round列表按课程聚合,封装成「课程+对应回合列表」的结构后传递给前端:
@GetMapping("/rounds/{id}") public String roundsHome(@PathVariable(value = "id") Long id, Model model) { List<Course> courses = courseService.getAllCourses(); List<Round> rounds = userService.getUserById(id).getRounds(); // 按课程ID分组,避免同一课程的不同实例导致重复分组 Map<Long, CourseWithRounds> courseRoundMap = new HashMap<>(); for (Round round : rounds) { Course course = round.getCourse(); courseRoundMap.computeIfAbsent(course.getId(), k -> new CourseWithRounds(course, new ArrayList<>())) .getRounds().add(round); } // 将分组后的数据转为列表传到前端 model.addAttribute("courseWithRounds", new ArrayList<>(courseRoundMap.values())); model.addAttribute("courses", courses); return "/discgolf/round/rounds"; } // 自定义DTO,封装课程和对应的回合列表 static class CourseWithRounds { private Course course; private List<Round> rounds; public CourseWithRounds(Course course, List<Round> rounds) { this.course = course; this.rounds = rounds; } // 必须提供getter方法供Thymeleaf调用 public Course getCourse() { return course; } public List<Round> getRounds() { return rounds; } }
2. 前端HTML修改:生成课程手风琴+嵌套回合内容
遍历分组后的课程数据,每个课程生成一个手风琴项,折叠面板内遍历该课程下的所有回合。注意要为每个手风琴元素生成唯一ID,避免Bootstrap功能冲突:
<div class="container"> <div> <a>Rounds Played</a> </div> <div id="accordion"> <!-- 遍历每个带回合的课程 --> <div th:each="courseGroup : ${courseWithRounds}" class="card"> <div class="card-header" th:id="'heading-' + ${courseGroup.course.id}"> <h5 class="mb-0"> <button class="btn btn-link" data-toggle="collapse" th:data-target="'#collapse-' + ${courseGroup.course.id}" aria-expanded="false" th:aria-controls="'collapse-' + ${courseGroup.course.id}"> <div> <label>Course: </label> <label th:text="${courseGroup.course.name}"></label> <label th:text="' (' + ${#lists.size(courseGroup.rounds)} + ' rounds)'"></label> </div> </button> </h5> </div> <div th:id="'collapse-' + ${courseGroup.course.id}" class="collapse" th:aria-labelledby="'heading-' + ${courseGroup.course.id}" data-parent="#accordion"> <div class="card-body"> <!-- 遍历当前课程下的所有回合 --> <div th:each="round : ${courseGroup.rounds}" class="mb-4 border-bottom pb-3"> <div class="d-flex flex-row"> <div class="p-3"> <label>Date: </label> <label th:text="${#dates.format(round.roundDate, 'dd-MMM-yyyy')}"></label> </div> <div class="p-3"> <label>Score: </label> <label th:text="${round.total - round.course.par}"></label> </div> </div> <br> <div> <table class="table table-bordered w-auto"> <tr> <th th:text="${'Hole'}"></th> <th th:each="hole : ${courseGroup.course.holes}" th:text="${hole.number}"></th> <th th:text="${'Total'}"></th> </tr> <tr> <td th:text="${'Par'}"></td> <td th:each="par : ${courseGroup.course.holes}" th:text="${par.par}"></td> <td th:text="${courseGroup.course.par}"></td> </tr> <tr> <td th:text="${'Score'}"></td> <td th:each="score : ${round.scores}" th:style="${score.score > score.holePar} ? 'background-color: red' : (${score.score < score.holePar } ? 'background-color: blue' : 'background-color: #eee' ) " th:text="${score.score}"></td> <td th:text="${round.total}"></td> </tr> </table> <br> <a th:href="@{/discgolf/deleteRound/{id}(id=${round.roundId})}" title="Remove Round" data-target="#deleteRoundModal" class="table-link danger"> <span class="fa-stack"> <i class="fa fa-square fa-stack-2x"></i> <i class="fa fa-trash-o fa-stack-1x fa-inverse" title="Delete this round"></i> </span> </a> </div> </div> </div> </div> </div> </div> </div>
核心原因说明
你当前的代码直接遍历平铺的rounds列表,会为每个回合创建一个手风琴卡片,导致同一课程出现多个重复标题。分组后,每个课程只生成一个手风琴标题,点击展开的是该课程下的所有回合,完全匹配你的需求。
内容的提问来源于stack exchange,提问作者Kayd Anderson
相关产品推荐
相关产品推荐

