You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 08:19:55